summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.rspec2
-rw-r--r--.travis.yml11
-rw-r--r--CHANGELOG74
-rw-r--r--CONTRIBUTING.md67
-rw-r--r--Gemfile25
-rw-r--r--Gemfile.lock95
-rw-r--r--LICENSE2
-rw-r--r--MAINTENANCE.md2
-rw-r--r--PROCESS.md10
-rw-r--r--Procfile2
-rw-r--r--README.md58
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/authbuttons/github_32.png (renamed from vendor/assets/images/authbuttons/github_32.png)bin1902 -> 1902 bytes
-rw-r--r--app/assets/images/authbuttons/github_64.png (renamed from vendor/assets/images/authbuttons/github_64.png)bin4444 -> 4444 bytes
-rw-r--r--app/assets/images/authbuttons/google_32.png (renamed from vendor/assets/images/authbuttons/google_32.png)bin1611 -> 1611 bytes
-rw-r--r--app/assets/images/authbuttons/google_64.png (renamed from vendor/assets/images/authbuttons/google_64.png)bin3437 -> 3437 bytes
-rw-r--r--app/assets/images/authbuttons/twitter_32.png (renamed from vendor/assets/images/authbuttons/twitter_32.png)bin1417 -> 1417 bytes
-rw-r--r--app/assets/images/authbuttons/twitter_64.png (renamed from vendor/assets/images/authbuttons/twitter_64.png)bin3328 -> 3328 bytes
-rw-r--r--app/assets/images/bg_fallback.png (renamed from vendor/assets/images/bg_fallback.png)bin2976 -> 2976 bytes
-rw-r--r--app/assets/images/icon_sprite.png (renamed from vendor/assets/images/icon_sprite.png)bin2782 -> 2782 bytes
-rw-r--r--app/assets/images/progress_bar.gif (renamed from vendor/assets/images/progress_bar.gif)bin494 -> 494 bytes
-rw-r--r--app/assets/images/slider_handles.png (renamed from vendor/assets/images/slider_handles.png)bin4122 -> 4122 bytes
-rw-r--r--app/assets/images/ui-icons_222222_256x240.png (renamed from vendor/assets/images/ui-icons_222222_256x240.png)bin4193 -> 4193 bytes
-rw-r--r--app/assets/images/ui-icons_454545_256x240.png (renamed from vendor/assets/images/ui-icons_454545_256x240.png)bin4193 -> 4193 bytes
-rw-r--r--app/assets/javascripts/api.js.coffee18
-rw-r--r--app/assets/javascripts/application.js31
-rw-r--r--app/assets/javascripts/application.js.coffee (renamed from app/assets/javascripts/main.js.coffee)53
-rw-r--r--app/assets/javascripts/behaviors/details_behavior.coffee12
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.coffee15
-rw-r--r--app/assets/javascripts/commit.js.coffee2
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee2
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee15
-rw-r--r--app/assets/javascripts/issue.js.coffee9
-rw-r--r--app/assets/javascripts/issues.js.coffee6
-rw-r--r--app/assets/javascripts/merge_request.js.coffee103
-rw-r--r--app/assets/javascripts/merge_requests.js.coffee96
-rw-r--r--app/assets/javascripts/project_users_select.js.coffee63
-rw-r--r--app/assets/javascripts/users_select.js.coffee4
-rw-r--r--app/assets/stylesheets/application.scss7
-rw-r--r--app/assets/stylesheets/generic/blocks.scss15
-rw-r--r--app/assets/stylesheets/generic/buttons.scss28
-rw-r--r--app/assets/stylesheets/generic/common.scss168
-rw-r--r--app/assets/stylesheets/generic/flash.scss25
-rw-r--r--app/assets/stylesheets/generic/forms.scss26
-rw-r--r--app/assets/stylesheets/generic/issue_box.scss76
-rw-r--r--app/assets/stylesheets/generic/lists.scss12
-rw-r--r--app/assets/stylesheets/generic/selects.scss47
-rw-r--r--app/assets/stylesheets/generic/typography.scss13
-rw-r--r--app/assets/stylesheets/gl_bootstrap.scss2
-rw-r--r--app/assets/stylesheets/highlight/white.scss6
-rw-r--r--app/assets/stylesheets/main/layout.scss4
-rw-r--r--app/assets/stylesheets/main/mixins.scss3
-rw-r--r--app/assets/stylesheets/main/variables.scss1
-rw-r--r--app/assets/stylesheets/print.scss13
-rw-r--r--app/assets/stylesheets/sections/admin.scss6
-rw-r--r--app/assets/stylesheets/sections/commits.scss346
-rw-r--r--app/assets/stylesheets/sections/dashboard.scss5
-rw-r--r--app/assets/stylesheets/sections/diff.scss350
-rw-r--r--app/assets/stylesheets/sections/events.scss10
-rw-r--r--app/assets/stylesheets/sections/groups.scss9
-rw-r--r--app/assets/stylesheets/sections/header.scss17
-rw-r--r--app/assets/stylesheets/sections/issues.scss62
-rw-r--r--app/assets/stylesheets/sections/merge_requests.scss10
-rw-r--r--app/assets/stylesheets/sections/nav.scss20
-rw-r--r--app/assets/stylesheets/sections/notes.scss52
-rw-r--r--app/assets/stylesheets/sections/profile.scss11
-rw-r--r--app/assets/stylesheets/sections/tree.scss2
-rw-r--r--app/assets/stylesheets/themes/ui_color.scss4
-rw-r--r--app/controllers/admin/background_jobs_controller.rb3
-rw-r--r--app/controllers/admin/users_controller.rb4
-rw-r--r--app/controllers/application_controller.rb25
-rw-r--r--app/controllers/dashboard_controller.rb10
-rw-r--r--app/controllers/groups_controller.rb41
-rw-r--r--app/controllers/passwords_controller.rb18
-rw-r--r--app/controllers/profiles/emails_controller.rb26
-rw-r--r--app/controllers/profiles/keys_controller.rb2
-rw-r--r--app/controllers/projects/edit_tree_controller.rb24
-rw-r--r--app/controllers/projects/issues_controller.rb6
-rw-r--r--app/controllers/projects/labels_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb27
-rw-r--r--app/controllers/projects/milestones_controller.rb3
-rw-r--r--app/controllers/projects/notes_controller.rb2
-rw-r--r--app/controllers/projects/raw_controller.rb18
-rw-r--r--app/controllers/projects_controller.rb77
-rw-r--r--app/controllers/public/projects_controller.rb2
-rw-r--r--app/controllers/search_controller.rb1
-rw-r--r--app/controllers/snippets_controller.rb3
-rw-r--r--app/controllers/users_controller.rb6
-rw-r--r--app/finders/README.md22
-rw-r--r--app/finders/base_finder.rb (renamed from app/services/filtering_service.rb)17
-rw-r--r--app/finders/issues_finder.rb22
-rw-r--r--app/finders/merge_requests_finder.rb22
-rw-r--r--app/finders/notes_finder.rb17
-rw-r--r--app/finders/projects_finder.rb63
-rw-r--r--app/helpers/application_helper.rb21
-rw-r--r--app/helpers/commits_helper.rb91
-rw-r--r--app/helpers/gitlab_markdown_helper.rb17
-rw-r--r--app/helpers/groups_helper.rb8
-rw-r--r--app/helpers/issues_helper.rb16
-rw-r--r--app/helpers/merge_requests_helper.rb5
-rw-r--r--app/helpers/notifications_helper.rb6
-rw-r--r--app/helpers/profile_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb25
-rw-r--r--app/helpers/search_helper.rb15
-rw-r--r--app/helpers/selects_helper.rb20
-rw-r--r--app/helpers/tree_helper.rb2
-rw-r--r--app/mailers/emails/groups.rb2
-rw-r--r--app/mailers/emails/issues.rb24
-rw-r--r--app/mailers/emails/merge_requests.rb26
-rw-r--r--app/mailers/emails/notes.rb20
-rw-r--r--app/mailers/emails/profile.rb8
-rw-r--r--app/mailers/emails/projects.rb11
-rw-r--r--app/mailers/notify.rb33
-rw-r--r--app/models/ability.rb17
-rw-r--r--app/models/commit.rb16
-rw-r--r--app/models/concerns/issuable.rb5
-rw-r--r--app/models/diff_line.rb3
-rw-r--r--app/models/email.rb33
-rw-r--r--app/models/event.rb14
-rw-r--r--app/models/group.rb6
-rw-r--r--app/models/issue.rb7
-rw-r--r--app/models/key.rb2
-rw-r--r--app/models/merge_request.rb27
-rw-r--r--app/models/merge_request_diff.rb6
-rw-r--r--app/models/milestone.rb5
-rw-r--r--app/models/namespace.rb10
-rw-r--r--app/models/note.rb6
-rw-r--r--app/models/notification.rb27
-rw-r--r--app/models/project.rb104
-rw-r--r--app/models/project_hook.rb3
-rw-r--r--app/models/project_services/assembla_service.rb1
-rw-r--r--app/models/project_services/campfire_service.rb1
-rw-r--r--app/models/project_services/emails_on_push_service.rb1
-rw-r--r--app/models/project_services/flowdock_service.rb1
-rw-r--r--app/models/project_services/gemnasium_service.rb54
-rw-r--r--app/models/project_services/gitlab_ci_service.rb3
-rw-r--r--app/models/project_services/hipchat_service.rb1
-rw-r--r--app/models/project_services/pivotaltracker_service.rb1
-rw-r--r--app/models/project_services/slack_message.rb95
-rw-r--r--app/models/project_services/slack_service.rb68
-rw-r--r--app/models/repository.rb21
-rw-r--r--app/models/service.rb5
-rw-r--r--app/models/snippet.rb2
-rw-r--r--app/models/tree.rb7
-rw-r--r--app/models/user.rb31
-rw-r--r--app/models/web_hook.rb4
-rw-r--r--app/observers/activity_observer.rb39
-rw-r--r--app/observers/base_observer.rb4
-rw-r--r--app/observers/email_observer.rb5
-rw-r--r--app/observers/issue_observer.rb3
-rw-r--r--app/observers/merge_request_observer.rb25
-rw-r--r--app/observers/milestone_observer.rb13
-rw-r--r--app/observers/note_observer.rb6
-rw-r--r--app/observers/project_observer.rb26
-rw-r--r--app/services/event_create_service.rb64
-rw-r--r--app/services/git_push_service.rb4
-rw-r--r--app/services/git_tag_push_service.rb40
-rw-r--r--app/services/merge_requests/auto_merge_service.rb5
-rw-r--r--app/services/merge_requests/base_merge_service.rb10
-rw-r--r--app/services/merge_requests/merge_service.rb5
-rw-r--r--app/services/notes/create_service.rb1
-rw-r--r--app/services/notes/load_service.rb20
-rw-r--r--app/services/notification_service.rb14
-rw-r--r--app/services/projects/create_service.rb23
-rw-r--r--app/services/search/global_service.rb9
-rw-r--r--app/views/admin/background_jobs/show.html.haml18
-rw-r--r--app/views/admin/dashboard/index.html.haml251
-rw-r--r--app/views/admin/groups/index.html.haml27
-rw-r--r--app/views/admin/groups/show.html.haml5
-rw-r--r--app/views/admin/hooks/_data_ex.html.haml67
-rw-r--r--app/views/admin/hooks/index.html.haml6
-rw-r--r--app/views/admin/projects/index.html.haml9
-rw-r--r--app/views/admin/projects/show.html.haml5
-rw-r--r--app/views/admin/users/index.html.haml31
-rw-r--r--app/views/admin/users/show.html.haml5
-rw-r--r--app/views/dashboard/_activities.html.haml2
-rw-r--r--app/views/dashboard/_groups.html.haml2
-rw-r--r--app/views/dashboard/_projects.html.haml2
-rw-r--r--app/views/dashboard/_zero_authorized_projects.html.haml17
-rw-r--r--app/views/dashboard/projects.html.haml2
-rwxr-xr-x[-rw-r--r--]app/views/devise/confirmations/new.html.haml3
-rwxr-xr-x[-rw-r--r--]app/views/devise/passwords/new.html.haml3
-rw-r--r--app/views/devise/sessions/_oauth_providers.html.haml4
-rw-r--r--app/views/devise/sessions/new.html.haml2
-rw-r--r--app/views/groups/_filter.html.haml2
-rw-r--r--app/views/groups/_new_group_member.html.haml7
-rw-r--r--app/views/groups/_projects.html.haml2
-rw-r--r--app/views/groups/edit.html.haml7
-rw-r--r--app/views/groups/issues.html.haml4
-rw-r--r--app/views/groups/members.html.haml40
-rw-r--r--app/views/groups/merge_requests.html.haml4
-rw-r--r--app/views/groups/show.html.haml22
-rw-r--r--app/views/help/_api_layout.html.haml2
-rw-r--r--app/views/help/permissions.html.haml222
-rw-r--r--app/views/help/public_access.html.haml63
-rw-r--r--app/views/help/ssh.html.haml30
-rw-r--r--app/views/help/system_hooks.html.haml13
-rw-r--r--app/views/help/web_hooks.html.haml117
-rw-r--r--app/views/help/workflow.html.haml39
-rw-r--r--app/views/layouts/_head.html.haml4
-rw-r--r--app/views/layouts/_head_panel.html.haml4
-rw-r--r--app/views/layouts/_init_auto_complete.html.haml3
-rw-r--r--app/views/layouts/_piwik.html.haml12
-rw-r--r--app/views/layouts/_public_head_panel.html.haml16
-rw-r--r--app/views/layouts/admin.html.haml1
-rw-r--r--app/views/layouts/nav/_group.html.haml6
-rw-r--r--app/views/layouts/nav/_profile.html.haml4
-rw-r--r--app/views/layouts/navless.html.haml1
-rw-r--r--app/views/layouts/notify.html.haml34
-rw-r--r--app/views/layouts/public.html.haml1
-rw-r--r--app/views/layouts/public_group.html.haml10
-rw-r--r--app/views/layouts/public_projects.html.haml5
-rw-r--r--app/views/layouts/public_users.html.haml1
-rw-r--r--app/views/layouts/search.html.haml1
-rw-r--r--app/views/layouts/user_team.html.haml1
-rw-r--r--app/views/notify/_note_message.html.haml6
-rw-r--r--app/views/notify/closed_issue_email.html.haml3
-rw-r--r--app/views/notify/closed_merge_request_email.html.haml9
-rw-r--r--app/views/notify/group_access_granted_email.html.haml1
-rw-r--r--app/views/notify/issue_status_changed_email.html.haml3
-rw-r--r--app/views/notify/merged_merge_request_email.html.haml9
-rw-r--r--app/views/notify/new_email_email.html.haml10
-rw-r--r--app/views/notify/new_email_email.text.erb7
-rw-r--r--app/views/notify/new_issue_email.html.haml15
-rw-r--r--app/views/notify/new_merge_request_email.html.haml14
-rw-r--r--app/views/notify/note_commit_email.html.haml3
-rw-r--r--app/views/notify/note_issue_email.html.haml3
-rw-r--r--app/views/notify/note_merge_request_email.html.haml13
-rw-r--r--app/views/notify/note_wall_email.html.haml4
-rw-r--r--app/views/notify/reassigned_issue_email.html.haml3
-rw-r--r--app/views/notify/reassigned_merge_request_email.html.haml3
-rw-r--r--app/views/notify/repository_push_email.html.haml6
-rw-r--r--app/views/notify/repository_push_email.text.haml4
-rw-r--r--app/views/profiles/emails/index.html.haml33
-rw-r--r--app/views/profiles/groups/index.html.haml4
-rw-r--r--app/views/profiles/keys/index.html.haml2
-rw-r--r--app/views/profiles/notifications/_settings.html.haml44
-rw-r--r--app/views/profiles/notifications/show.html.haml81
-rw-r--r--app/views/profiles/passwords/new.html.haml4
-rw-r--r--app/views/projects/blob/_text.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml11
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml8
-rw-r--r--app/views/projects/commits/_commit.html.haml9
-rw-r--r--app/views/projects/commits/_diffs.html.haml45
-rw-r--r--app/views/projects/commits/_head.html.haml2
-rw-r--r--app/views/projects/commits/_image.html.haml6
-rw-r--r--app/views/projects/commits/_parallel_view.html.haml114
-rw-r--r--app/views/projects/commits/_text_file.html.haml2
-rw-r--r--app/views/projects/compare/show.html.haml8
-rw-r--r--app/views/projects/create.js.haml8
-rw-r--r--app/views/projects/deploy_keys/index.html.haml4
-rw-r--r--app/views/projects/deploy_keys/show.html.haml2
-rw-r--r--app/views/projects/edit.html.haml172
-rw-r--r--app/views/projects/edit_tree/show.html.haml5
-rw-r--r--app/views/projects/empty.html.haml70
-rw-r--r--app/views/projects/hooks/index.html.haml13
-rw-r--r--app/views/projects/import.html.haml30
-rw-r--r--app/views/projects/issues/_form.html.haml6
-rw-r--r--app/views/projects/issues/_head.html.haml6
-rw-r--r--app/views/projects/issues/_issue.html.haml6
-rw-r--r--app/views/projects/issues/_issue_context.html.haml23
-rw-r--r--app/views/projects/issues/_issues.html.haml140
-rw-r--r--app/views/projects/issues/index.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml40
-rw-r--r--app/views/projects/issues/update.js.haml3
-rw-r--r--app/views/projects/labels/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/_form.html.haml62
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml10
-rw-r--r--app/views/projects/merge_requests/_show.html.haml13
-rw-r--r--app/views/projects/merge_requests/branch_from.js.haml4
-rw-r--r--app/views/projects/merge_requests/branch_to.js.haml2
-rw-r--r--app/views/projects/merge_requests/index.html.haml100
-rw-r--r--app/views/projects/merge_requests/show/_commits.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_context.html.haml22
-rw-r--r--app/views/projects/merge_requests/show/_diffs.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_mr_accept.html.haml34
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml68
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml29
-rw-r--r--app/views/projects/merge_requests/update.js.haml2
-rw-r--r--app/views/projects/milestones/_milestone.html.haml2
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml29
-rw-r--r--app/views/projects/new.html.haml61
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply.html.haml2
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml2
-rw-r--r--app/views/projects/notes/_discussion.html.haml17
-rw-r--r--app/views/projects/notes/_discussion_diff.html.haml45
-rw-r--r--app/views/projects/notes/_form.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml2
-rw-r--r--app/views/projects/protected_branches/index.html.haml2
-rw-r--r--app/views/projects/repositories/_download_archive.html.haml14
-rw-r--r--app/views/projects/show.html.haml6
-rw-r--r--app/views/projects/snippets/index.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml2
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/team_members/_group_members.html.haml8
-rw-r--r--app/views/projects/team_members/index.html.haml4
-rw-r--r--app/views/projects/tree/_readme.html.haml4
-rw-r--r--app/views/projects/walls/show.html.haml2
-rw-r--r--app/views/projects/wikis/_main_links.html.haml4
-rw-r--r--app/views/projects/wikis/_nav.html.haml2
-rw-r--r--app/views/public/projects/index.html.haml2
-rw-r--r--app/views/search/_project_results.html.haml2
-rw-r--r--app/views/shared/_filter.html.haml23
-rw-r--r--app/views/shared/_issues.html.haml2
-rw-r--r--app/views/shared/_merge_requests.html.haml2
-rw-r--r--app/views/shared/_project_filter.html.haml20
-rw-r--r--app/views/shared/_promo.html.haml4
-rw-r--r--app/views/shared/_sort_dropdown.html.haml2
-rw-r--r--app/views/snippets/_blob_content.html.haml2
-rw-r--r--app/views/snippets/_snippets.html.haml2
-rw-r--r--app/views/snippets/current_user_index.html.haml4
-rw-r--r--app/views/snippets/index.html.haml4
-rw-r--r--app/views/users/show.html.haml4
-rw-r--r--app/views/users_groups/_users_group.html.haml49
-rw-r--r--app/workers/post_receive.rb12
-rw-r--r--app/workers/repository_import_worker.rb4
-rwxr-xr-xbin/bundle3
-rwxr-xr-xbin/rails8
-rwxr-xr-xbin/rake8
-rwxr-xr-xbin/rspec7
-rwxr-xr-xbin/spinach7
-rwxr-xr-xbin/spring18
-rw-r--r--config/application.rb6
-rw-r--r--config/database.yml.mysql1
-rw-r--r--config/environments/production.rb6
-rw-r--r--config/gitlab.yml.example24
-rw-r--r--config/initializers/2_app.rb2
-rw-r--r--config/initializers/6_rack_profiler.rb6
-rw-r--r--config/initializers/devise.rb2
-rw-r--r--config/initializers/gemoji.rb3
-rw-r--r--config/routes.rb5
-rw-r--r--config/unicorn_development.rb2
-rw-r--r--db/fixtures/development/01_admin.rb5
-rw-r--r--db/fixtures/development/04_project.rb94
-rw-r--r--db/fixtures/development/05_users.rb1
-rw-r--r--db/fixtures/development/06_teams.rb2
-rw-r--r--db/fixtures/development/09_issues.rb8
-rw-r--r--db/fixtures/development/10_merge_requests.rb7
-rw-r--r--db/fixtures/development/11_keys.rb4
-rw-r--r--db/fixtures/development/12_snippets.rb2
-rw-r--r--db/fixtures/development/13_comments.rb19
-rw-r--r--db/migrate/20130809124851_add_permission_check_to_user.rb5
-rw-r--r--db/migrate/20140209025651_create_emails.rb13
-rw-r--r--db/migrate/20140214102325_add_api_key_to_services.rb5
-rw-r--r--db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb5
-rw-r--r--db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb5
-rw-r--r--db/migrate/20140312145357_add_import_status_to_project.rb5
-rw-r--r--db/migrate/20140313092127_migrate_already_imported_projects.rb12
-rw-r--r--db/schema.rb114
-rw-r--r--doc/README.md17
-rw-r--r--doc/api/README.md51
-rw-r--r--doc/api/commits.md95
-rw-r--r--doc/api/merge_requests.md42
-rw-r--r--doc/api/projects.md37
-rw-r--r--doc/api/repositories.md134
-rw-r--r--doc/api/repository_files.md102
-rw-r--r--doc/api/system_hooks.md140
-rw-r--r--doc/development/README.md2
-rw-r--r--doc/development/architecture.md8
-rw-r--r--doc/development/shell_commands.md117
-rw-r--r--doc/install/README.md4
-rw-r--r--doc/install/database_mysql.md (renamed from doc/install/databases.md)37
-rw-r--r--doc/install/installation.md48
-rw-r--r--doc/install/requirements.md18
-rw-r--r--doc/integration/external-issue-tracker.md2
-rw-r--r--doc/legal/README.md2
-rw-r--r--doc/markdown/markdown.md47
-rw-r--r--doc/permissions/permissions.md45
-rw-r--r--doc/public_access/public_access.md28
-rw-r--r--doc/raketasks/README.md6
-rw-r--r--doc/raketasks/user_management.md2
-rw-r--r--doc/release/README.md2
-rw-r--r--doc/release/monthly.md14
-rw-r--r--doc/release/security.md2
-rw-r--r--doc/security/README.md2
-rw-r--r--doc/ssh/deploy_keys.md12
-rw-r--r--doc/ssh/ssh.md24
-rw-r--r--doc/system_hooks/system_hooks.md89
-rw-r--r--doc/update/4.2-to-5.0.md2
-rw-r--r--doc/update/5.0-to-5.1.md2
-rw-r--r--doc/update/5.1-to-5.2.md2
-rw-r--r--doc/update/5.1-to-5.4.md3
-rw-r--r--doc/update/5.1-to-6.0.md2
-rw-r--r--doc/update/5.2-to-5.3.md2
-rw-r--r--doc/update/5.3-to-5.4.md3
-rw-r--r--doc/update/5.4-to-6.0.md2
-rw-r--r--doc/update/6.0-to-6.1.md2
-rw-r--r--doc/update/6.0-to-6.7.md (renamed from doc/update/6.0-to-6.5.md)38
-rw-r--r--doc/update/6.1-to-6.2.md3
-rw-r--r--doc/update/6.5-to-6.6.md94
-rw-r--r--doc/update/6.6-to-6.7.md98
-rw-r--r--doc/update/README.md5
-rw-r--r--doc/update/mysql-to-postgresql.md9
-rw-r--r--doc/update/patch_versions.md11
-rw-r--r--doc/update/ruby.md2
-rw-r--r--doc/update/upgrader.md12
-rw-r--r--doc/web_hooks/web_hooks.md114
-rw-r--r--doc/workflow/workflow.md26
-rw-r--r--features/admin/users.feature6
-rw-r--r--features/group.feature3
-rw-r--r--features/profile/emails.feature25
-rw-r--r--features/project/archived.feature (renamed from features/project/archived_projects.feature)0
-rw-r--r--features/project/commits/branches.feature12
-rw-r--r--features/project/commits/comments.feature (renamed from features/project/commits/commit_comments.feature)0
-rw-r--r--features/project/commits/diff_comments.feature (renamed from features/project/commits/commit_diff_comments.feature)0
-rw-r--r--features/project/commits/user_lookup.feature14
-rw-r--r--features/project/create.feature (renamed from features/project/create_project.feature)3
-rw-r--r--features/project/fork.feature (renamed from features/project/fork_project.feature)0
-rw-r--r--features/project/forked_merge_requests.feature6
-rw-r--r--features/project/issues/filter_labels.feature26
-rw-r--r--features/project/merge_requests.feature52
-rw-r--r--features/project/public.feature (renamed from features/project/public_projects.feature)0
-rw-r--r--features/project/service.feature6
-rw-r--r--features/project/source/markdown_render.feature38
-rw-r--r--features/public/projects.feature (renamed from features/public/public_projects.feature)0
-rw-r--r--features/public/public_groups.feature118
-rw-r--r--features/snippets/discover.feature (renamed from features/snippets/discover_snippets.feature)0
-rw-r--r--features/snippets/user.feature (renamed from features/snippets/user_snippets.feature)0
-rw-r--r--features/steps/admin/active_tab.rb (renamed from features/steps/admin/admin_active_tab.rb)0
-rw-r--r--features/steps/admin/broadcast_messages.rb (renamed from features/steps/admin/admin_broadcast_messages.rb)0
-rw-r--r--features/steps/admin/groups.rb (renamed from features/steps/admin/admin_groups.rb)0
-rw-r--r--features/steps/admin/logs.rb (renamed from features/steps/admin/admin_logs.rb)0
-rw-r--r--features/steps/admin/projects.rb (renamed from features/steps/admin/admin_projects.rb)0
-rw-r--r--features/steps/admin/users.rb (renamed from features/steps/admin/admin_users.rb)13
-rw-r--r--features/steps/dashboard/active_tab.rb (renamed from features/steps/dashboard/dashboard_active_tab.rb)0
-rw-r--r--features/steps/dashboard/dashboard.rb2
-rw-r--r--features/steps/dashboard/event_filters.rb (renamed from features/steps/dashboard/dashboard_event_filters.rb)0
-rw-r--r--features/steps/dashboard/issues.rb (renamed from features/steps/dashboard/dashboard_issues.rb)0
-rw-r--r--features/steps/dashboard/merge_requests.rb (renamed from features/steps/dashboard/dashboard_merge_requests.rb)0
-rw-r--r--features/steps/dashboard/projects.rb (renamed from features/steps/dashboard/dashboard_projects.rb)0
-rw-r--r--features/steps/dashboard/search.rb (renamed from features/steps/dashboard/dashboard_search.rb)0
-rw-r--r--features/steps/dashboard/with_archived_projects.rb (renamed from features/steps/dashboard/dashboard_with_archived_projects.rb)0
-rw-r--r--features/steps/group/group.rb1
-rw-r--r--features/steps/profile/active_tab.rb (renamed from features/steps/profile/profile_active_tab.rb)0
-rw-r--r--features/steps/profile/emails.rb48
-rw-r--r--features/steps/profile/notifications.rb (renamed from features/steps/profile/profile_notifications.rb)1
-rw-r--r--features/steps/profile/ssh_keys.rb (renamed from features/steps/profile/profile_ssh_keys.rb)0
-rw-r--r--features/steps/project/active_tab.rb (renamed from features/steps/project/project_active_tab.rb)0
-rw-r--r--features/steps/project/archived.rb (renamed from features/steps/project/project_archived.rb)0
-rw-r--r--features/steps/project/browse_branches.rb51
-rw-r--r--features/steps/project/browse_commits.rb (renamed from features/steps/project/project_browse_commits.rb)0
-rw-r--r--features/steps/project/browse_commits_user_lookup.rb35
-rw-r--r--features/steps/project/browse_files.rb (renamed from features/steps/project/project_browse_files.rb)0
-rw-r--r--features/steps/project/browse_git_repo.rb (renamed from features/steps/project/project_browse_git_repo.rb)0
-rw-r--r--features/steps/project/browse_tags.rb (renamed from features/steps/project/project_browse_tags.rb)0
-rw-r--r--features/steps/project/create.rb (renamed from features/steps/project/create_project.rb)2
-rw-r--r--features/steps/project/filter_labels.rb70
-rw-r--r--features/steps/project/fork.rb (renamed from features/steps/project/project_fork.rb)0
-rw-r--r--features/steps/project/forked_merge_requests.rb (renamed from features/steps/project/project_forked_merge_requests.rb)10
-rw-r--r--features/steps/project/graph.rb (renamed from features/steps/project/project_graph.rb)0
-rw-r--r--features/steps/project/hooks.rb (renamed from features/steps/project/project_hooks.rb)0
-rw-r--r--features/steps/project/issue_tracker.rb (renamed from features/steps/project/project_issue_tracker.rb)0
-rw-r--r--features/steps/project/issues.rb (renamed from features/steps/project/project_issues.rb)0
-rw-r--r--features/steps/project/labels.rb (renamed from features/steps/project/project_labels.rb)0
-rw-r--r--features/steps/project/markdown_render.rb (renamed from features/steps/project/project_markdown_render.rb)86
-rw-r--r--features/steps/project/merge_requests.rb (renamed from features/steps/project/project_merge_requests.rb)98
-rw-r--r--features/steps/project/milestones.rb (renamed from features/steps/project/project_milestones.rb)0
-rw-r--r--features/steps/project/multiselect_blob.rb (renamed from features/steps/project/project_multiselect_blob.rb)0
-rw-r--r--features/steps/project/network_graph.rb (renamed from features/steps/project/project_network_graph.rb)0
-rw-r--r--features/steps/project/project_browse_branches.rb35
-rw-r--r--features/steps/project/public.rb (renamed from features/steps/project/public_projects.rb)0
-rw-r--r--features/steps/project/redirects.rb2
-rw-r--r--features/steps/project/search_code.rb (renamed from features/steps/project/project_search_code.rb)0
-rw-r--r--features/steps/project/services.rb (renamed from features/steps/project/project_services.rb)18
-rw-r--r--features/steps/project/snippets.rb (renamed from features/steps/project/project_snippets.rb)0
-rw-r--r--features/steps/project/team_management.rb (renamed from features/steps/project/project_team_management.rb)0
-rw-r--r--features/steps/project/wall.rb (renamed from features/steps/project/project_wall.rb)0
-rw-r--r--features/steps/project/wiki.rb (renamed from features/steps/project/project_wiki.rb)0
-rw-r--r--features/steps/public/groups_feature.rb93
-rw-r--r--features/steps/public/projects.rb (renamed from features/steps/public/projects_feature.rb)2
-rw-r--r--features/steps/shared/diff_note.rb42
-rw-r--r--features/steps/shared/project.rb8
-rw-r--r--features/steps/snippets/discover.rb (renamed from features/steps/snippets/discover_snippets.rb)0
-rw-r--r--features/steps/snippets/user.rb (renamed from features/steps/snippets/user_snippets.rb)0
-rw-r--r--features/support/env.rb2
-rw-r--r--lib/api/api.rb4
-rw-r--r--lib/api/commits.rb64
-rw-r--r--lib/api/entities.rb52
-rw-r--r--lib/api/files.rb53
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/api/internal.rb52
-rw-r--r--lib/api/merge_requests.rb24
-rw-r--r--lib/api/project_hooks.rb9
-rw-r--r--lib/api/project_members.rb114
-rw-r--r--lib/api/projects.rb128
-rw-r--r--lib/api/repositories.rb48
-rw-r--r--lib/backup/database.rb3
-rw-r--r--lib/backup/manager.rb33
-rw-r--r--lib/backup/repository.rb3
-rw-r--r--lib/backup/uploads.rb5
-rw-r--r--lib/gitlab/backend/grack_auth.rb106
-rw-r--r--lib/gitlab/backend/grack_helpers.rb28
-rw-r--r--lib/gitlab/backend/shell.rb16
-rw-r--r--lib/gitlab/git_access.rb74
-rw-r--r--lib/gitlab/ldap/access.rb23
-rw-r--r--lib/gitlab/ldap/adapter.rb86
-rw-r--r--lib/gitlab/ldap/person.rb50
-rw-r--r--lib/gitlab/ldap/user.rb28
-rw-r--r--lib/gitlab/logger.rb6
-rw-r--r--lib/gitlab/markdown.rb8
-rw-r--r--lib/gitlab/popen.rb14
-rw-r--r--lib/gitlab/regex.rb4
-rw-r--r--lib/gitlab/satellite/satellite.rb19
-rw-r--r--lib/gitlab/seeder.rb19
-rw-r--r--lib/gitlab/upgrader.rb28
-rw-r--r--lib/redcarpet/render/gitlab_html.rb8
-rwxr-xr-xlib/support/deploy/deploy.sh4
-rwxr-xr-xlib/support/init.d/gitlab34
-rw-r--r--lib/support/logrotate/gitlab10
-rw-r--r--lib/support/nginx/gitlab10
-rw-r--r--lib/tasks/dev.rake6
-rw-r--r--lib/tasks/gitlab/check.rake51
-rw-r--r--lib/tasks/gitlab/db/drop_all_tables.rake10
-rw-r--r--lib/tasks/gitlab/generate_docs.rake2
-rw-r--r--lib/tasks/gitlab/info.rake12
-rw-r--r--lib/tasks/gitlab/setup.rake8
-rw-r--r--lib/tasks/gitlab/shell.rake12
-rw-r--r--lib/tasks/gitlab/task_helpers.rake15
-rw-r--r--lib/tasks/gitlab/test.rake10
-rw-r--r--lib/tasks/migrate/add_limits_mysql.rake14
-rw-r--r--lib/tasks/sidekiq.rake8
-rw-r--r--lib/tasks/spec.rake14
-rw-r--r--lib/tasks/spinach.rake14
-rw-r--r--lib/tasks/test.rake6
-rwxr-xr-xscript/background_jobs28
-rwxr-xr-xscript/web2
-rw-r--r--spec/controllers/profile_keys_controller_spec.rb10
-rw-r--r--spec/factories.rb41
-rw-r--r--spec/features/issues_spec.rb61
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb49
-rw-r--r--spec/features/security/group/group_access_spec.rb (renamed from spec/features/security/group_access_spec.rb)1
-rw-r--r--spec/features/security/group/internal_group_access_spec.rb82
-rw-r--r--spec/features/security/group/mixed_group_access_spec.rb83
-rw-r--r--spec/features/security/group/public_group_access_spec.rb82
-rw-r--r--spec/features/security/project/internal_access_spec.rb7
-rw-r--r--spec/finders/issues_finder_spec.rb (renamed from spec/services/filtering_service_spec.rb)37
-rw-r--r--spec/finders/merge_requests_finder_spec.rb33
-rw-r--r--spec/finders/projects_finder_spec.rb51
-rw-r--r--spec/helpers/application_helper_spec.rb40
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb8
-rw-r--r--spec/helpers/notifications_helper_spec.rb6
-rw-r--r--spec/lib/gitlab/ldap/ldap_user_auth_spec.rb3
-rw-r--r--spec/lib/gitlab/popen_spec.rb20
-rw-r--r--spec/mailers/notify_spec.rb170
-rw-r--r--spec/models/assembla_service_spec.rb1
-rw-r--r--spec/models/concerns/issuable_spec.rb5
-rw-r--r--spec/models/flowdock_service_spec.rb1
-rw-r--r--spec/models/gemnasium_service_spec.rb47
-rw-r--r--spec/models/gitlab_ci_service_spec.rb1
-rw-r--r--spec/models/gollum_wiki_spec.rb6
-rw-r--r--spec/models/project_hook_spec.rb19
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/service_spec.rb1
-rw-r--r--spec/models/slack_message_spec.rb56
-rw-r--r--spec/models/slack_service_spec.rb69
-rw-r--r--spec/models/user_spec.rb14
-rw-r--r--spec/models/wiki_page_spec.rb6
-rw-r--r--spec/observers/activity_observer_spec.rb61
-rw-r--r--spec/observers/email_observer_spec.rb17
-rw-r--r--spec/observers/merge_request_observer_spec.rb2
-rw-r--r--spec/observers/users_project_observer_spec.rb24
-rw-r--r--spec/requests/api/commits_spec.rb87
-rw-r--r--spec/requests/api/files_spec.rb30
-rw-r--r--spec/requests/api/issues_spec.rb12
-rw-r--r--spec/requests/api/merge_requests_spec.rb26
-rw-r--r--spec/requests/api/project_members_spec.rb156
-rw-r--r--spec/requests/api/projects_spec.rb206
-rw-r--r--spec/requests/api/repositories_spec.rb71
-rw-r--r--spec/routing/routing_spec.rb17
-rw-r--r--spec/seed_project.tar.gzbin9833961 -> 9769010 bytes
-rw-r--r--spec/services/event_create_service_spec.rb103
-rw-r--r--spec/services/git_tag_push_service_spec.rb47
-rw-r--r--spec/services/notification_service_spec.rb35
-rw-r--r--spec/services/projects/create_service_spec.rb (renamed from spec/services/projects_create_service_spec.rb)21
-rw-r--r--spec/services/projects/transfer_service_spec.rb (renamed from spec/services/project_transfer_service_spec.rb)0
-rw-r--r--spec/services/projects/update_service_spec.rb (renamed from spec/services/projects_update_service_spec.rb)0
-rw-r--r--spec/services/search_service_spec.rb33
-rw-r--r--spec/spec_helper.rb101
-rw-r--r--spec/support/test_env.rb23
-rw-r--r--spec/support/valid_commit.rb1
-rw-r--r--spec/support/valid_commit_with_alt_email.rb6
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb4
585 files changed, 8692 insertions, 4404 deletions
diff --git a/.gitignore b/.gitignore
index 4c25c8abf82..aa822771ec2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,4 @@ doc/code/*
*.log
public/uploads.*
public/assets/
+.envrc
diff --git a/.rspec b/.rspec
index 7488cbe7792..4e1e0d2f722 100644
--- a/.rspec
+++ b/.rspec
@@ -1 +1 @@
---color --drb
+--color
diff --git a/.travis.yml b/.travis.yml
index 75b4c5c7030..6bff3752b2a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,12 +1,14 @@
language: ruby
env:
global:
- - DB=mysql
- TRAVIS=true
matrix:
- - TASK=spinach
- - TASK=spec
- - TASK=jasmine:ci
+ - TASK=spinach DB=mysql
+ - TASK=spec DB=mysql
+ - TASK=jasmine:ci DB=mysql
+ - TASK=spinach DB=postgresql
+ - TASK=spec DB=postgresql
+ - TASK=jasmine:ci DB=postgresql
before_install:
- sudo apt-get install libicu-dev -y
branches:
@@ -15,7 +17,6 @@ branches:
rvm:
- 2.0.0
services:
- - mysql
- redis-server
before_script:
- "cp config/database.yml.$DB config/database.yml"
diff --git a/CHANGELOG b/CHANGELOG
index 9933f2f77d4..19219915789 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,68 @@
+v 6.8.0
+ - Ability to at mention users that are participating in issue and merge req. discussion
+ - Enabled GZip Compression for assets in example Nginx, make sure that Nginx is compiled with --with-http_gzip_static_module flag (this is default in Ubuntu)
+ - Make user search case-insensitive (Christopher Arnold)
+ - Remove omniauth-ldap nickname bug workaround
+ - Drop all tables before restoring a Postgres backup
+
+v 6.7.2
+ - Fix upgrader script
+
+v 6.7.1
+ - Fix GitLab CI integration
+
+v 6.7.0
+ - Increased the example Nginx client_max_body_size from 5MB to 20MB, consider updating it manually on existing installations
+ - Add support for Gemnasium as a Project Service (Olivier Gonzalez)
+ - Add edit file button to MergeRequest diff
+ - Public groups (Jason Hollingsworth)
+ - Cleaner headers in Notification Emails (Pierre de La Morinerie)
+ - Blob and tree gfm links to anchors work
+ - Piwik Integration (Sebastian Winkler)
+ - Show contribution guide link for new issue form (Jeroen van Baarsen)
+ - Fix CI status for merge requests from fork
+ - Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
+ - New page load indicator that includes a spinner that scrolls with the page
+ - Converted all the help sections into markdown
+ - LDAP user filters
+ - Streamline the content of notification emails (Pierre de La Morinerie)
+ - Fixes a bug with group member administration (Matt DeTullio)
+ - Sort tag names using VersionSorter (Robert Speicher)
+ - Add GFM autocompletion for MergeRequests (Robert Speicher)
+ - Add webhook when a new tag is pushed (Jeroen van Baarsen)
+ - Add button for toggling inline comments in diff view
+ - Add retry feature for repository import
+ - Reuse the GitLab LDAP connection within each request
+ - Changed markdown new line behaviour to conform to markdown standards
+ - Fix global search
+ - Faster authorized_keys rebuilding in `rake gitlab:shell:setup` (requires gitlab-shell 1.8.5)
+ - Create and Update MR calls now support the description parameter (Greg Messner)
+ - Markdown relative links in the wiki link to wiki pages, markdown relative links in repositories link to files in the repository
+ - Added Slack service integration (Federico Ravasio)
+ - Better API responses for access_levels (sponsored by O'Reilly Media)
+ - Requires at least 2 unicorn workers
+ - Requires gitlab-shell v1.9+
+ - Replaced gemoji(due to closed licencing problem) with Phantom Open Emoji library(combined SIL Open Font License, MIT License and the CC 3.0 License)
+ - Fix `/:username.keys` response content type (Dmitry Medvinsky)
+
+v 6.6.5
+ - Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
+ - Hide mr close button for comment form if merge request was closed or inline comment
+ - Adds ability to reopen closed merge request
+
+v 6.6.4
+ - Add missing html escape for highlighted code blocks in comments, issues
+
+v 6.6.3
+ - Fix 500 error when edit yourself from admin area
+ - Hide private groups for public profiles
+
+v 6.6.2
+ - Fix 500 error on branch/tag create or remove via UI
+
+v 6.6.1
+ - Fix 500 error on files tab if submodules presents
+
v 6.6.0
- Retrieving user ssh keys publically(github style): http://__HOST__/__USERNAME__.keys
- Permissions: Developer now can manage issue tracker (modify any issue)
@@ -12,9 +77,16 @@ v 6.6.0
- Remove snippet expiration
- Mobile UI improvements (Drew Blessing)
- Fix block/remove UI for admin::users#show page
- - Show users' group membership on users' activity page
+ - Show users' group membership on users' activity page (Robert Djurasaj)
- User pages are visible without login if user is authorized to a public project
- Markdown rendered headers have id derived from their name and link to their id
+ - Improve application to work faster with large groups (100+ members)
+ - Multiple emails per user
+ - Show last commit for file when view file source
+ - Restyle Issue#show page and MR#show page
+ - Ability to filter by multiple labels for Issues page
+ - Rails version to 4.0.3
+ - Fixed attachment identifier displaying underneath note text (Jason Blanchard)
v 6.5.1
- Fix branch selectbox when create merge request from fork
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cf52808b74b..bdf5e09831a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -22,11 +22,14 @@ Issues and merge requests should be in English and contain appropriate language
## Issue tracker
-To get support for your particular problem please use the channels as detailed in the getting help section of [the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/).
+To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/).
-The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious bugs or misbehavior in the latest [stable or development release of GitLab](MAINTENANCE.md). When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
+The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md).
+If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request.
+When submitting an issue please conform to the issue submission guidelines listed below.
+Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
-Do not use the issue tracker for feature requests. We have a specific [feedback and suggestions forum](http://feedback.gitlab.com) for this purpose.
+Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose.
Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there.
@@ -48,7 +51,7 @@ Please send a merge request with a tested solution or a merge request with a fai
## Merge requests
-We welcome merge requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a merge request for are listed with the [status 'accepting merge/merge requests' on our feedback forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome.
+We welcome merge requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a merge request for are listed with the [status 'accepting merge requests' on our feature request forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome. If you want to add a new feature that is not marked it is best to first create a feedback issue (if there isn't one already) and leave a comment asking for it to be marked accepting merge requests. Please include screenshots or wireframes if the feature will also change the UI.
### Merge request guidelines
@@ -60,31 +63,45 @@ If you can, please submit a merge request with the fix or improvements including
1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork
-1. Submit a merge request (MR)
+1. Submit a merge request (MR) to the master branch
1. The MR title should describes the change you want to make
1. The MR description should give a motive for your change and the method you used to achieve it
1. If the MR changes the UI it should include before and after screenshots
-1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feedback items](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR
+1. If the MR changes CSS classes please include the list of affected pages `grep css-class ./app -R`
+1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feature requests](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR
1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submittion
+1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md).
+
+The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab.com team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? The smaller a MR is the more likely it is it will be merged, after that you can send more MR's to enhance it.
-The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month.
-The best time to submit a MR and get feedback fast.
-Before this time the GitLab.com team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud.
-After the 7th it is already getting closer to the release date of the next version.
-This means there is less time to fix the issues created by merging large new features.
-
-We will accept a merge requests if it:
-
-* Includes proper tests and all tests pass (unless it contains a test exposing a bug in existing code)
-* Can be merged without problems (if not please use: `git rebase master`)
-* Do not break any existing functionality
-* Conforms to the [Ruby](https://github.com/bbatsov/ruby-style-guide) and [Rails](https://github.com/bbatsov/rails-style-guide) style guides and best practices
-* Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed)
-* Keeps the GitLab code base clean and well structured
-* Contains functionality we think other users will benefit from too
-* Doesn't add configuration options since these complicate future changes
-* Contains a single commit (please use `git rebase -i` to squash commits)
-
-For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed).
+For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). Please ensure that your merge request meets the following contribution acceptance criteria.
+
+**Please format your merge request description as follows:**
+
+1. What does this MR do?
+2. Are there points in the code the reviewer needs to double check?
+3. Why was this MR needed?
+4. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)?
+5. Screenshots (If appropiate)
+
+## Contribution acceptance criteria
+
+1. The change is as small as possible (see the above paragraph for details)
+1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code)
+1. Can merge without problems (if not please use: `git rebase master`)
+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. 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
+1. Contains a single commit (please use `git rebase -i` to squash commits)
+1. It conforms to the following style guides
+
+## Style guides
+
+1. [Ruby style guide](https://github.com/bbatsov/ruby-style-guide)
+1. [Rails style guide](https://github.com/bbatsov/rails-style-guide)
+1. [CoffeeScript style guide](https://github.com/polarmobile/coffeescript-style-guide)
+1. [Shell command guidelines](doc/development/shell_commands.md)
diff --git a/Gemfile b/Gemfile
index 6a06df6fbe5..397165f668f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -15,6 +15,9 @@ gem 'rails-observers'
gem 'actionpack-page_caching'
gem 'actionpack-action_caching'
+# Default values for AR models
+gem "default_value_for", "~> 3.0.0"
+
# Supported DBs
gem "mysql2", group: :mysql
gem "pg", group: :postgres
@@ -29,7 +32,7 @@ gem 'omniauth-github'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 5.4.0'
+gem "gitlab_git", '~> 5.7.1'
# Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
@@ -45,7 +48,8 @@ gem "gitlab-linguist", "~> 3.0.0", require: "linguist"
# API
gem "grape", "~> 0.6.1"
-gem "grape-entity", "~> 0.3.0"
+# Replace with rubygems when nesteted entities get released
+gem "grape-entity", "~> 0.4.1", ref: 'd904381c951e86250c3f44213b349a3dd8e83fb1', git: 'https://github.com/intridea/grape-entity.git'
gem 'rack-cors', require: 'rack/cors'
# Email validation
@@ -111,6 +115,7 @@ gem 'settingslogic'
# Misc
gem "foreman"
+gem 'version_sorter'
# Cache
gem "redis-rails"
@@ -124,6 +129,12 @@ gem "hipchat", "~> 0.14.0"
# Flowdock integration
gem "gitlab-flowdock-git-hook", "~> 0.4.2"
+# Gemnasium integration
+gem "gemnasium-gitlab-service", "~> 0.2"
+
+# Slack integration
+gem "slack-notifier", "~> 0.2.0"
+
# d3
gem "d3_rails", "~> 3.1.4"
@@ -154,14 +165,15 @@ gem "modernizr", "2.6.2"
gem "raphael-rails", "~> 2.1.2"
gem 'bootstrap-sass', '~> 3.0'
gem "font-awesome-rails", '~> 3.2'
-gem "gemoji", "~> 1.3.0"
+gem "gitlab_emoji", "~> 0.0.1.1"
gem "gon", '~> 5.0.0'
+gem 'nprogress-rails'
group :development do
gem "annotate", "~> 2.6.0.beta2"
gem "letter_opener"
gem 'quiet_assets', '~> 1.0.1'
- gem 'rack-mini-profiler'
+ gem 'rack-mini-profiler', require: false
# Better errors handler
gem 'better_errors'
@@ -206,8 +218,11 @@ group :development, :test do
# PhantomJS driver for Capybara
gem 'poltergeist', '~> 1.4.1'
- gem 'spork', '~> 1.0rc'
gem 'jasmine', '2.0.0.rc5'
+
+ gem "spring", '1.1.1'
+ gem "spring-commands-rspec", '1.0.1'
+ gem "spring-commands-spinach", '1.0.0'
end
group :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 2c99063726e..1a0bce98ac5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -5,15 +5,24 @@ GIT
specs:
github-markup (0.7.6)
+GIT
+ remote: https://github.com/intridea/grape-entity.git
+ revision: d904381c951e86250c3f44213b349a3dd8e83fb1
+ ref: d904381c951e86250c3f44213b349a3dd8e83fb1
+ specs:
+ grape-entity (0.4.1)
+ activesupport
+ multi_json (>= 1.3.2)
+
GEM
remote: https://rubygems.org/
specs:
ace-rails-ap (2.0.1)
- actionmailer (4.0.2)
- actionpack (= 4.0.2)
+ actionmailer (4.0.3)
+ actionpack (= 4.0.3)
mail (~> 2.5.4)
- actionpack (4.0.2)
- activesupport (= 4.0.2)
+ actionpack (4.0.3)
+ activesupport (= 4.0.3)
builder (~> 3.1.0)
erubis (~> 2.7.0)
rack (~> 1.5.2)
@@ -22,16 +31,16 @@ GEM
actionpack (>= 4.0.0, < 5.0)
actionpack-page_caching (1.0.2)
actionpack (>= 4.0.0, < 5)
- activemodel (4.0.2)
- activesupport (= 4.0.2)
+ activemodel (4.0.3)
+ activesupport (= 4.0.3)
builder (~> 3.1.0)
- activerecord (4.0.2)
- activemodel (= 4.0.2)
+ activerecord (4.0.3)
+ activemodel (= 4.0.3)
activerecord-deprecated_finders (~> 1.0.2)
- activesupport (= 4.0.2)
+ activesupport (= 4.0.3)
arel (~> 4.0.0)
activerecord-deprecated_finders (1.0.3)
- activesupport (4.0.2)
+ activesupport (4.0.3)
i18n (~> 0.6, >= 0.6.4)
minitest (~> 4.2)
multi_json (~> 1.3)
@@ -43,7 +52,7 @@ GEM
annotate (2.6.0)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
- arel (4.0.1)
+ arel (4.0.2)
asciidoctor (0.1.4)
atomic (1.1.14)
awesome_print (1.2.0)
@@ -101,6 +110,8 @@ GEM
daemons (1.1.9)
database_cleaner (1.2.0)
debug_inspector (0.0.2)
+ default_value_for (3.0.0)
+ activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.3)
devise (3.0.4)
bcrypt-ruby (~> 3.0)
@@ -117,6 +128,8 @@ GEM
mail (~> 2.2)
email_validator (1.4.0)
activemodel
+ emoji (1.0.1)
+ json
enumerize (0.7.0)
activesupport (>= 3.2)
equalizer (0.0.8)
@@ -152,7 +165,8 @@ GEM
dotenv (>= 0.7)
thor (>= 0.13.6)
formatador (0.2.4)
- gemoji (1.3.1)
+ gemnasium-gitlab-service (0.2.1)
+ rugged (~> 0.19)
gherkin-ruby (0.3.1)
racc
github-markdown (0.5.5)
@@ -177,7 +191,9 @@ GEM
charlock_holmes (~> 0.6.6)
escape_utils (~> 0.2.4)
mime-types (~> 1.19)
- gitlab_git (5.4.0)
+ gitlab_emoji (0.0.1.1)
+ emoji (~> 1.0.1)
+ gitlab_git (5.7.1)
activesupport (~> 4.0.0)
charlock_holmes (~> 0.6.9)
gitlab-grit (~> 2.6.1)
@@ -202,9 +218,6 @@ GEM
rack-accept
rack-mount
virtus (>= 1.0.0)
- grape-entity (0.3.0)
- activesupport
- multi_json (>= 1.3.2)
growl (1.0.3)
guard (2.2.4)
formatador (>= 0.2.4)
@@ -231,7 +244,7 @@ GEM
httparty
httparty
http_parser.rb (0.5.3)
- httparty (0.12.0)
+ httparty (0.13.0)
json (~> 1.8)
multi_xml (>= 0.5.2)
httpauth (0.2.0)
@@ -287,6 +300,7 @@ GEM
net-ssh (>= 1.99.1)
net-ssh (2.7.0)
nokogiri (1.5.10)
+ nprogress-rails (0.1.2.3)
oauth (0.4.7)
oauth2 (0.8.1)
faraday (~> 0.8)
@@ -320,7 +334,7 @@ GEM
cliver (~> 0.2.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
- polyglot (0.3.3)
+ polyglot (0.3.4)
posix-spawn (0.3.8)
protected_attributes (1.0.5)
activemodel (>= 4.0.1, < 5.0)
@@ -338,7 +352,7 @@ GEM
rack-attack (2.3.0)
rack
rack-cors (0.2.9)
- rack-mini-profiler (0.1.31)
+ rack-mini-profiler (0.9.0)
rack (>= 1.1.3)
rack-mount (0.8.3)
rack (>= 1.0.0)
@@ -346,13 +360,13 @@ GEM
rack
rack-test (0.6.2)
rack (>= 1.0)
- rails (4.0.2)
- actionmailer (= 4.0.2)
- actionpack (= 4.0.2)
- activerecord (= 4.0.2)
- activesupport (= 4.0.2)
+ rails (4.0.3)
+ actionmailer (= 4.0.3)
+ actionpack (= 4.0.3)
+ activerecord (= 4.0.3)
+ activesupport (= 4.0.3)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.0.2)
+ railties (= 4.0.3)
sprockets-rails (~> 2.0.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
@@ -365,13 +379,13 @@ GEM
i18n
require_all
ruby-progressbar
- railties (4.0.2)
- actionpack (= 4.0.2)
- activesupport (= 4.0.2)
+ railties (4.0.3)
+ actionpack (= 4.0.3)
+ activesupport (= 4.0.3)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
raindrops (0.12.0)
- rake (10.1.0)
+ rake (10.1.1)
raphael-rails (2.1.2)
rb-fsevent (0.9.3)
rb-inotify (0.9.2)
@@ -458,6 +472,7 @@ GEM
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
six (0.2.0)
+ slack-notifier (0.2.0)
slim (2.0.2)
temple (~> 0.6.6)
tilt (>= 1.3.3, < 2.1)
@@ -469,7 +484,11 @@ GEM
capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
- spork (1.0.0rc4)
+ spring (1.1.1)
+ spring-commands-rspec (1.0.1)
+ spring (>= 0.9.1)
+ spring-commands-spinach (1.0.0)
+ spring (>= 0.9.1)
sprockets (2.10.1)
hike (~> 1.2)
multi_json (~> 1.0)
@@ -528,6 +547,7 @@ GEM
raindrops (~> 0.7)
unicorn-worker-killer (0.4.2)
unicorn (~> 4)
+ version_sorter (1.1.0)
virtus (1.0.1)
axiom-types (~> 0.0.5)
coercible (~> 1.0)
@@ -563,6 +583,7 @@ DEPENDENCIES
coveralls
d3_rails (~> 3.1.4)
database_cleaner
+ default_value_for (~> 3.0.0)
devise (= 3.0.4)
devise-async (= 0.8.0)
email_spec
@@ -573,18 +594,19 @@ DEPENDENCIES
fog (~> 1.3.1)
font-awesome-rails (~> 3.2)
foreman
- gemoji (~> 1.3.0)
+ gemnasium-gitlab-service (~> 0.2)
github-markup (~> 0.7.4)!
gitlab-flowdock-git-hook (~> 0.4.2)
gitlab-gollum-lib (~> 1.1.0)
gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0)
- gitlab_git (~> 5.4.0)
+ gitlab_emoji (~> 0.0.1.1)
+ gitlab_git (~> 5.7.1)
gitlab_meta (= 6.0)
gitlab_omniauth-ldap (= 1.0.4)
gon (~> 5.0.0)
grape (~> 0.6.1)
- grape-entity (~> 0.3.0)
+ grape-entity (~> 0.4.1)!
growl
guard-rspec
guard-spinach
@@ -602,6 +624,7 @@ DEPENDENCIES
minitest (~> 4.7.0)
modernizr (= 2.6.2)
mysql2
+ nprogress-rails
omniauth (~> 1.1.3)
omniauth-github
omniauth-google-oauth2
@@ -634,9 +657,12 @@ DEPENDENCIES
simplecov
sinatra
six
+ slack-notifier (~> 0.2.0)
slim
spinach-rails
- spork (~> 1.0rc)
+ spring (= 1.1.1)
+ spring-commands-rspec (= 1.0.1)
+ spring-commands-spinach (= 1.0.0)
stamp
state_machine
test_after_commit
@@ -648,4 +674,5 @@ DEPENDENCIES
underscore-rails (~> 1.4.4)
unicorn (~> 4.6.3)
unicorn-worker-killer
+ version_sorter
webmock
diff --git a/LICENSE b/LICENSE
index 7cecc2485f4..8ebd322ffc8 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011 Dmitriy Zaporozhets
+Copyright (c) 2011-2014 Dmitriy Zaporozhets
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/MAINTENANCE.md b/MAINTENANCE.md
index 0dca62974c3..95d9f0a5e7b 100644
--- a/MAINTENANCE.md
+++ b/MAINTENANCE.md
@@ -21,3 +21,5 @@ release where the minor version is increased numerically by increments of one
(eg. `5.0 -> 5.1`).
We encourage everyone to run the latest stable release to ensure that you can easily upgrade to the most secure and feature rich GitLab experience. In order to make sure you can easily run the most recent stable release, we are working hard to keep the update process simple and reliable.
+
+More information about the release procedures can be found in the doc/release directory.
diff --git a/PROCESS.md b/PROCESS.md
index bf757025c40..2266d50b238 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -12,7 +12,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
- Closes invalid issues and merge requests with a comment (duplicates, [feature requests](#feature-requests), [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.)
- Assigns appropriate [labels](#how-we-handle-issues)
- Asks for feedback from issue reporter/merge request initiator ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.)
-- Asks for feedback from the relevant developer(s) based on the [list of members and their specialities](http://gitlab.org/team/)
+- Asks for feedback from the relevant developer(s) based on the [list of members and their specialities](https://www.gitlab.com/core-team/)
- Monitors all issues/merge requests for feedback (but especially ones commented on since automatically watching them):
- Closes issues with no feedback from the reporter for two weeks
- Closes stale merge requests
@@ -24,8 +24,6 @@ Below we describe the contributing process to GitLab for two reasons. So that co
- Monitors for new merge requests (at least once a week)
- Manages their work queue by looking at issues and merge requests assigned to them
- Close fixed issues (via commit messages or manually)
-- Codes [new features](http://feedback.gitlab.com/forums/176466-general/filters/top)!
-- Response guidelines
- Be kind to people trying to contribute. Be aware that people can be a non-native or a native English speaker, they might not understand thing or they might be very sensitive to how your word things. Use emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to merge requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review).
## Priorities of the issue team
@@ -37,7 +35,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
## Mentioning people
-The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](http://gitlab.org/team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
+The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://www.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
## Workflow labels
@@ -73,7 +71,7 @@ Thanks for the issue report. Please reformat your issue to conform to the issue
### Feature requests
-Thanks for your interest in GitLab. We don't use the issue tracker for feature requests. Please use http://feedback.gitlab.com/ for this purpose or create a merge request implementing this feature. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
+Thank you for your interest in improving GitLab. We don't use the issue tracker for feature requests. Things that are wrong but are not a regression compared to older versions of GitLab are considered feature requests and not issues. Please the [feature request forum](http://feedback.gitlab.com/) for this purpose or create a merge request implementing this feature. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
### Issue report for old version
@@ -89,7 +87,7 @@ Please use ``` to format console output, logs, and code as it's very hard to rea
### Issue fixed in newer version
-Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(http://blog.gitlab.org/). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://github.com/gitlabhq/gitlabhq/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Improperly formatted merge request
diff --git a/Procfile b/Procfile
index 9003369c938..18df7e78f9b 100644
--- a/Procfile
+++ b/Procfile
@@ -1,2 +1,2 @@
-web: bundle exec unicorn_rails -p $PORT -E development
+web: bundle exec unicorn_rails -p $PORT -E development -c config/unicorn_development.rb
worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default,gitlab_shell
diff --git a/README.md b/README.md
index dcd303e08a8..2668ed8bac0 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
### Code status
-* [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
+* [![build status](https://ci.gitlab.org/projects/1/status.png?ref=master)](https://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
* [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
@@ -23,17 +23,17 @@
### Resources
-* GitLab.org community site: [Homepage](http://gitlab.org) | [Screenshots](http://gitlab.org/screenshots/) | [Blog](http://blog.gitlab.org/) | [Demo](http://demo.gitlabhq.com/users/sign_in)
+* [GitLab.com](https://www.gitlab.com/) includes information about [subscriptions](https://www.gitlab.com/subscription/), [consultancy](https://www.gitlab.com/consultancy/), the [community](https://www.gitlab.com/community/) and the [hosted GitLab Cloud](https://www.gitlab.com/cloud/).
-* GitLab.com commercial services: [Homepage](http://www.gitlab.com/) | [Subscription](http://www.gitlab.com/subscription/) | [Consultancy](http://www.gitlab.com/consultancy/) | [GitLab Cloud](http://www.gitlab.com/cloud/) | [Blog](http://blog.gitlab.com/)
+* [GitLab Enterprise Edition](https://www.gitlab.com/gitlab-ce/) offers additional features that are useful for larger organizations (100+ users).
-* [GitLab Enterprise Edition](https://www.gitlab.com/features/) offers additional features that are useful for larger organizations (100+ users).
+* [GitLab CI](https://www.gitlab.com/gitlab-ci/) is a continuous integration (CI) server that is easy to integrate with GitLab.
-* [GitLab CI](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/README.md) is a continuous integration (CI) server that is easy to integrate with GitLab.
+* Unofficial third-party [iPhone app](http://gitlabcontrol.com/) and [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en) for GitLab
### Requirements
-* Ubuntu/Debian**
+* Ubuntu/Debian/CentOS/RHEL**
* ruby 1.9.3+
* git 1.7.10+
* redis 2.0+
@@ -45,13 +45,17 @@
#### Official installation methods
-* [Manual installation guide for a production server](doc/install/installation.md)
+* [GitLab packages](https://www.gitlab.com/downloads/) These packages contain GitLab and all its depencies (Ruby, PostgreSQL, Redis, Nginx, Unicorn, etc.). They are made with [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md) that also contains the installation instructions.
+
+* [GitLab virtual machine images](https://www.gitlab.com/downloads/) contain an operating system and a preinstalled GitLab. They are made with [GitLab Packer](https://gitlab.com/gitlab-org/gitlab-packer/blob/master/README.md) that also contains the installation instructions.
* [GitLab Chef Cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md) This cookbook can be used both for development installations and production installations. If you want to [contribute](CONTRIBUTE.md) to GitLab we suggest you follow the [development installation on a virtual machine with Vagrant](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/doc/development.md) instructions to install all testing dependencies.
+* [Manual installation guide](doc/install/installation.md) This guide to set up a production server offers detailed and complete step-by-step instructions.
+
#### Third party one-click installers
-* [Digital Ocean 1-Click Application Install](https://www.digitalocean.com/blog_posts/host-your-git-repositories-in-55-seconds-with-gitlab) Have a new server up in 55 seconds. Digital Ocean uses SSD disks which is great for an IO intensive app such as GitLab.
+* [Digital Ocean 1-Click Application Install](https://www.digitalocean.com/blog_posts/host-your-git-repositories-in-55-seconds-with-gitlab) Have a new server up in 55 seconds. Digital Ocean uses SSD disks which is great for an IO intensive app such as GitLab. We recommend selecting a droplet with [1GB of memory](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/requirements.md).
* [BitNami one-click installers](http://bitnami.com/stack/gitlab) This package contains both GitLab and GitLab CI. It is available as installer, virtual machine or for cloud hosting providers (Amazon Web Services/Azure/etc.).
@@ -67,7 +71,7 @@ Since 2011 GitLab is released on the 22nd of every month. Every new release incl
It is recommended to follow a monthly upgrade schedule. Security releases come out when needed. For more information about the release process see the documentation for [monthly](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/release/monthly.md) and [security](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/release/security.md) releases.
-* Features that will be in the next releases are listed on [the feedback and suggestions 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).
+* Features that will be in the next releases are listed 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).
### Run in production mode
@@ -92,14 +96,9 @@ or start each component separately
### Run the tests
-* Seed the database
-
- bundle exec rake db:setup RAILS_ENV=test
- bundle exec rake db:seed_fu RAILS_ENV=test
-
* Run all tests
- bundle exec rake gitlab:test RAILS_ENV=test
+ bundle exec rake test
* [RSpec](http://rspec.info/) unit and functional tests
@@ -130,31 +129,4 @@ or start each component separately
### Getting help
-* [Maintenance policy](MAINTENANCE.md) specifies what versions are supported.
-
-* [Troubleshooting guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) contains solutions to common problems.
-
-* [Mailing list](https://groups.google.com/forum/#!forum/gitlabhq) and [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) are the best places to ask questions. For example you can use it if you have questions about: permission denied errors, invisible repos, can't clone/pull/push or with web hooks that don't fire. Please search for similar issues before posting your own, there's a good chance somebody else had the same issue you have now and has resolved it. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there to a fix.
-
-* [Feedback and suggestions forum](http://feedback.gitlab.com) is the place to propose and discuss new features for GitLab.
-
-* [Contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) describes how to submit merge requests and issues. Pull requests and issues not in line with the guidelines in this document will be closed.
-
-* [Support subscription](http://www.gitlab.com/subscription/) connects you to the knowledge of GitLab experts that will resolve your issues and answer your questions.
-
-* [Consultancy](http://www.gitlab.com/consultancy/) from the GitLab experts for installations, upgrades and customizations.
-
-* [#gitlab IRC channel](http://www.freenode.net/) on Freenode to get in touch with other GitLab users and get help, it's managed by James Newton (newton), Drew Blessing (dblessing), and Sam Gleske (sag47).
-
-* [Book](http://www.packtpub.com/gitlab-repository-management/book) written by GitLab enthusiast Jonathan M. Hethey is unofficial but it offers a good overview.
-
-* [Gitter](https://gitter.im/gitlabhq/gitlabhq#) here you can ask questions when you need help.
-
-
-### Getting in touch
-
-* [Core team](http://gitlab.org/team/)
-
-* [Contributors](http://contributors.gitlab.org/)
-
-* [Community](http://gitlab.org/community/)
+Please see [Getting help for GitLab](https://www.gitlab.com/getting-help/) on our website for the many options to get help.
diff --git a/VERSION b/VERSION
index c07edf251a3..38f2fa89925 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-6.6.0.pre
+6.8.0.pre
diff --git a/vendor/assets/images/authbuttons/github_32.png b/app/assets/images/authbuttons/github_32.png
index c56eef05eb9..c56eef05eb9 100644
--- a/vendor/assets/images/authbuttons/github_32.png
+++ b/app/assets/images/authbuttons/github_32.png
Binary files differ
diff --git a/vendor/assets/images/authbuttons/github_64.png b/app/assets/images/authbuttons/github_64.png
index 39de55bc796..39de55bc796 100644
--- a/vendor/assets/images/authbuttons/github_64.png
+++ b/app/assets/images/authbuttons/github_64.png
Binary files differ
diff --git a/vendor/assets/images/authbuttons/google_32.png b/app/assets/images/authbuttons/google_32.png
index 6225cc9c2d7..6225cc9c2d7 100644
--- a/vendor/assets/images/authbuttons/google_32.png
+++ b/app/assets/images/authbuttons/google_32.png
Binary files differ
diff --git a/vendor/assets/images/authbuttons/google_64.png b/app/assets/images/authbuttons/google_64.png
index 4d608f71008..4d608f71008 100644
--- a/vendor/assets/images/authbuttons/google_64.png
+++ b/app/assets/images/authbuttons/google_64.png
Binary files differ
diff --git a/vendor/assets/images/authbuttons/twitter_32.png b/app/assets/images/authbuttons/twitter_32.png
index 696eb02484d..696eb02484d 100644
--- a/vendor/assets/images/authbuttons/twitter_32.png
+++ b/app/assets/images/authbuttons/twitter_32.png
Binary files differ
diff --git a/vendor/assets/images/authbuttons/twitter_64.png b/app/assets/images/authbuttons/twitter_64.png
index 2893274766f..2893274766f 100644
--- a/vendor/assets/images/authbuttons/twitter_64.png
+++ b/app/assets/images/authbuttons/twitter_64.png
Binary files differ
diff --git a/vendor/assets/images/bg_fallback.png b/app/assets/images/bg_fallback.png
index d9066ad7d7b..d9066ad7d7b 100644
--- a/vendor/assets/images/bg_fallback.png
+++ b/app/assets/images/bg_fallback.png
Binary files differ
diff --git a/vendor/assets/images/icon_sprite.png b/app/assets/images/icon_sprite.png
index 9ad65fc443b..9ad65fc443b 100644
--- a/vendor/assets/images/icon_sprite.png
+++ b/app/assets/images/icon_sprite.png
Binary files differ
diff --git a/vendor/assets/images/progress_bar.gif b/app/assets/images/progress_bar.gif
index c3d43fa40b2..c3d43fa40b2 100644
--- a/vendor/assets/images/progress_bar.gif
+++ b/app/assets/images/progress_bar.gif
Binary files differ
diff --git a/vendor/assets/images/slider_handles.png b/app/assets/images/slider_handles.png
index a6d477033fa..a6d477033fa 100644
--- a/vendor/assets/images/slider_handles.png
+++ b/app/assets/images/slider_handles.png
Binary files differ
diff --git a/vendor/assets/images/ui-icons_222222_256x240.png b/app/assets/images/ui-icons_222222_256x240.png
index 8bc06cbf03b..8bc06cbf03b 100644
--- a/vendor/assets/images/ui-icons_222222_256x240.png
+++ b/app/assets/images/ui-icons_222222_256x240.png
Binary files differ
diff --git a/vendor/assets/images/ui-icons_454545_256x240.png b/app/assets/images/ui-icons_454545_256x240.png
index cfd1eaffaae..cfd1eaffaae 100644
--- a/vendor/assets/images/ui-icons_454545_256x240.png
+++ b/app/assets/images/ui-icons_454545_256x240.png
Binary files differ
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index 5f4a38ebbd0..fafa5cdfaa4 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -3,6 +3,7 @@
user_path: "/api/:version/users/:id.json"
notes_path: "/api/:version/projects/:id/notes.json"
namespaces_path: "/api/:version/namespaces.json"
+ project_users_path: "/api/:version/projects/:id/users.json"
# Get 20 (depends on api) recent notes
# and sort the ascending from oldest to newest
@@ -50,6 +51,23 @@
).done (users) ->
callback(users)
+ # Return project users list. Filtered by query
+ # Only active users retrieved
+ projectUsers: (project_id, query, callback) ->
+ url = Api.buildUrl(Api.project_users_path)
+ url = url.replace(':id', project_id)
+
+ $.ajax(
+ url: url
+ data:
+ private_token: gon.api_token
+ search: query
+ per_page: 20
+ active: true
+ dataType: "json"
+ ).done (users) ->
+ callback(users)
+
# Return namespaces list. Filtered by query
namespaces: (query, callback) ->
url = Api.buildUrl(Api.namespaces_path)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
deleted file mode 100644
index 4a393dbfe81..00000000000
--- a/app/assets/javascripts/application.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// This is a manifest file that'll be compiled into including all the files listed below.
-// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-// be included in the compiled file accessible from http://example.com/assets/application.js
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// the compiled file.
-//
-//= require jquery
-//= require jquery.ui.all
-//= require jquery_ujs
-//= require jquery.cookie
-//= require jquery.endless-scroll
-//= require jquery.highlight
-//= require jquery.history
-//= require jquery.waitforimages
-//= require jquery.atwho
-//= require jquery.scrollto
-//= require jquery.blockUI
-//= require turbolinks
-//= require jquery.turbolinks
-//= require bootstrap
-//= require modernizr
-//= require select2
-//= require raphael
-//= require g.raphael-min
-//= require g.bar-min
-//= require branch-graph
-//= require highlightjs.min
-//= require ace/ace
-//= require_tree .
-//= require d3
-//= require underscore
diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/application.js.coffee
index 9cf4dba815b..5042221abe4 100644
--- a/app/assets/javascripts/main.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -1,3 +1,37 @@
+# This is a manifest file that'll be compiled into including all the files listed below.
+# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+# be included in the compiled file accessible from http://example.com/assets/application.js
+# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+# the compiled file.
+#
+#= require jquery
+#= require jquery.ui.all
+#= require jquery_ujs
+#= require jquery.cookie
+#= require jquery.endless-scroll
+#= require jquery.highlight
+#= require jquery.history
+#= require jquery.waitforimages
+#= require jquery.atwho
+#= require jquery.scrollto
+#= require jquery.blockUI
+#= require turbolinks
+#= require jquery.turbolinks
+#= require bootstrap
+#= require modernizr
+#= require select2
+#= require raphael
+#= require g.raphael-min
+#= require g.bar-min
+#= require branch-graph
+#= require highlightjs.min
+#= require ace/ace
+#= require d3
+#= require underscore
+#= require nprogress
+#= require nprogress-turbolinks
+#= require_tree .
+
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
@@ -41,19 +75,11 @@ window.linkify = (str) ->
window.simpleFormat = (str) ->
linkify(sanitize(str).replace(/\n/g, '<br />'))
-window.startSpinner = ->
- $('.turbolink-spinner').fadeIn()
-
-window.stopSpinner = ->
- $('.turbolink-spinner').fadeOut()
-
window.unbindEvents = ->
$(document).unbind('scroll')
$(document).off('scroll')
-document.addEventListener("page:fetch", startSpinner)
document.addEventListener("page:fetch", unbindEvents)
-document.addEventListener("page:change", stopSpinner)
$ ->
# Click a .one_click_select field, select the contents
@@ -62,11 +88,6 @@ $ ->
$('.remove-row').bind 'ajax:success', ->
$(this).closest('li').fadeOut()
- # Click a .appear-link, appear-data fadeout
- $(".appear-link").on 'click', (e) ->
- $('.appear-data').fadeIn()
- e.preventDefault()
-
# Initialize select2 selects
$('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
@@ -117,14 +138,10 @@ $ ->
# Commit show suppressed diff
- $(".content").on "click", ".supp_diff_link", ->
+ $(".diff-content").on "click", ".supp_diff_link", ->
$(@).next('table').show()
$(@).remove()
- $(".content").on "click", ".js-details-expand", ->
- $(@).next('.js-details-contain').removeClass("hide")
- $(@).remove()
-
(($) ->
# Disable an element and add the 'disabled' Bootstrap class
$.fn.extend disable: ->
diff --git a/app/assets/javascripts/behaviors/details_behavior.coffee b/app/assets/javascripts/behaviors/details_behavior.coffee
index 7ad5c818946..decab3e1bed 100644
--- a/app/assets/javascripts/behaviors/details_behavior.coffee
+++ b/app/assets/javascripts/behaviors/details_behavior.coffee
@@ -1,5 +1,15 @@
$ ->
$("body").on "click", ".js-details-target", ->
container = $(@).closest(".js-details-container")
-
container.toggleClass("open")
+
+ # Show details content. Hides link after click.
+ #
+ # %div
+ # %a.js-details-expand
+ # %div.js-details-content
+ #
+ $("body").on "click", ".js-details-expand", (e) ->
+ $(@).next('.js-details-content').removeClass("hide")
+ $(@).hide()
+ e.preventDefault()
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee
index 5afb656e696..d06cb116dfe 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.coffee
+++ b/app/assets/javascripts/behaviors/toggler_behavior.coffee
@@ -1,17 +1,18 @@
$ ->
$("body").on "click", ".js-toggler-target", ->
container = $(@).closest(".js-toggler-container")
-
container.toggleClass("on")
- $("body").on "click", ".js-toggle-visibility-link", (e) ->
+ # Toggle button. Show/hide content inside parent container.
+ # Button does not change visibility. If button has icon - it changes chevron style.
+ #
+ # %div.js-toggle-container
+ # %a.js-toggle-button
+ # %div.js-toggle-content
+ #
+ $("body").on "click", ".js-toggle-button", (e) ->
$(@).find('i').
toggleClass('icon-chevron-down').
toggleClass('icon-chevron-up')
- container = $(".js-toggle-visibility-container")
- container.toggleClass("hide")
- e.preventDefault()
-
- $("body").on "click", ".js-toggle-button", (e) ->
$(@).closest(".js-toggle-container").find(".js-toggle-content").toggle()
e.preventDefault()
diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee
index 9f55a1e6368..5f53439ca4b 100644
--- a/app/assets/javascripts/commit.js.coffee
+++ b/app/assets/javascripts/commit.js.coffee
@@ -1,6 +1,6 @@
class Commit
constructor: ->
- $('.files .file').each ->
+ $('.files .diff-file').each ->
new CommitFile(this)
@Commit = Commit
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 9afb5974858..46d6db0f05c 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -19,6 +19,8 @@ class Dispatcher
switch page
when 'projects:issues:index'
Issues.init()
+ when 'projects:issues:show'
+ new Issue()
when 'projects:issues:new', 'projects:merge_requests:new'
GitLab.GfmAutoComplete.setup()
when 'dashboard:show'
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 77091da8f61..00d56ae5b4b 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -6,13 +6,13 @@ GitLab.GfmAutoComplete =
dataSource: ''
# Emoji
Emoji:
- assetBase: ''
template: '<li data-value="${insert}">${name} <img alt="${name}" height="20" src="${image}" width="20" /></li>'
# Team Members
Members:
template: '<li data-value="${username}">${username} <small>${name}</small></li>'
+ # Issues and MergeRequests
Issues:
template: '<li data-value="${id}"><small>${id}</small> ${title} </li>'
@@ -26,7 +26,7 @@ GitLab.GfmAutoComplete =
tpl: @Emoji.template
callbacks:
before_save: (emojis) =>
- $.map emojis, (em) => name: em, insert: em+ ':', image: "#{@Emoji.assetBase}/#{em}.png"
+ $.map emojis, (em) => name: em.name, insert: em.name+ ':', image: em.path
# Team Members
input.atwho
@@ -46,11 +46,22 @@ GitLab.GfmAutoComplete =
before_save: (issues) ->
$.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}"
+ input.atwho
+ at: '!'
+ alias: 'mergerequests'
+ search_key: 'search'
+ tpl: @Issues.template
+ callbacks:
+ before_save: (merges) ->
+ $.map merges, (m) -> id: m.iid, title: sanitize(m.title), search: "#{m.iid} #{m.title}"
+
input.one "focus", =>
$.getJSON(@dataSource).done (data) ->
# load members
input.atwho 'load', "@", data.members
# load issues
input.atwho 'load', "issues", data.issues
+ # load merge requests
+ input.atwho 'load', "mergerequests", data.mergerequests
# load emojis
input.atwho 'load', ":", data.emojis
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
new file mode 100644
index 00000000000..36935a0a159
--- /dev/null
+++ b/app/assets/javascripts/issue.js.coffee
@@ -0,0 +1,9 @@
+class Issue
+ constructor: ->
+ $('.edit-issue.inline-update input[type="submit"]').hide()
+ $(".issue-box .inline-update").on "change", "select", ->
+ $(this).submit()
+ $(".issue-box .inline-update").on "change", "#issue_assignee_id", ->
+ $(this).submit()
+
+@Issue = Issue
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index 6c239c66c0a..e2f1bc743f7 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -77,9 +77,3 @@
$("#update_issues_ids").val []
$(".issues_bulk_update").hide()
$(".issues-filters").show()
-
-$ ->
- $('.edit-issue.inline-update input[type="submit"]').hide();
- $("body").on "change", ".edit-issue.inline-update select", ->
- $(this).submit()
-
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
new file mode 100644
index 00000000000..c0f83c93021
--- /dev/null
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -0,0 +1,103 @@
+class MergeRequest
+ constructor: (@opts) ->
+ @initContextWidget()
+ this.$el = $('.merge-request')
+ @diffs_loaded = if @opts.action == 'diffs' then true else false
+ @commits_loaded = false
+
+ this.activateTab(@opts.action)
+
+ this.bindEvents()
+
+ this.initMergeWidget()
+ this.$('.show-all-commits').on 'click', =>
+ this.showAllCommits()
+
+ modal = $('#modal_merge_info').modal(show: false)
+
+ disableButtonIfEmptyField '#merge_commit_message', '.accept_merge_request'
+
+
+ # Local jQuery finder
+ $: (selector) ->
+ this.$el.find(selector)
+
+ initContextWidget: ->
+ $('.edit-merge_request.inline-update input[type="submit"]').hide()
+ $(".issue-box .inline-update").on "change", "select", ->
+ $(this).submit()
+ $(".issue-box .inline-update").on "change", "#merge_request_assignee_id", ->
+ $(this).submit()
+
+ initMergeWidget: ->
+ this.showState( @opts.current_status )
+
+ if this.$('.automerge_widget').length and @opts.check_enable
+ $.get @opts.url_to_automerge_check, (data) =>
+ this.showState( data.merge_status )
+ , 'json'
+
+ if @opts.ci_enable
+ $.get @opts.url_to_ci_check, (data) =>
+ this.showCiState data.status
+ , 'json'
+
+ bindEvents: ->
+ this.$('.nav-tabs').on 'click', 'a', (event) =>
+ a = $(event.currentTarget)
+
+ href = a.attr('href')
+ History.replaceState {path: href}, document.title, href
+
+ event.preventDefault()
+
+ this.$('.nav-tabs').on 'click', 'li', (event) =>
+ this.activateTab($(event.currentTarget).data('action'))
+
+ this.$('.accept_merge_request').on 'click', ->
+ $('.automerge_widget.can_be_merged').hide()
+ $('.merge-in-progress').show()
+
+ activateTab: (action) ->
+ this.$('.nav-tabs li').removeClass 'active'
+ this.$('.tab-content').hide()
+ switch action
+ when 'diffs'
+ this.$('.nav-tabs .diffs-tab').addClass 'active'
+ this.loadDiff() unless @diffs_loaded
+ this.$('.diffs').show()
+ else
+ this.$('.nav-tabs .notes-tab').addClass 'active'
+ this.$('.notes').show()
+
+ showState: (state) ->
+ $('.automerge_widget').hide()
+ $('.automerge_widget.' + state).show()
+
+ showCiState: (state) ->
+ $('.ci_widget').hide()
+ $('.ci_widget.ci-' + state).show()
+
+ loadDiff: (event) ->
+ $.ajax
+ type: 'GET'
+ url: this.$('.nav-tabs .diffs-tab a').attr('href')
+ beforeSend: =>
+ this.$('.status').addClass 'loading'
+ complete: =>
+ @diffs_loaded = true
+ this.$('.status').removeClass 'loading'
+ success: (data) =>
+ this.$(".diffs").html(data.html)
+ dataType: 'json'
+
+ showAllCommits: ->
+ this.$('.first-commits').remove()
+ this.$('.all-commits').removeClass 'hide'
+
+ alreadyOrCannotBeMerged: ->
+ this.$('.automerge_widget').hide()
+ this.$('.merge-in-progress').hide()
+ this.$('.automerge_widget.already_cannot_be_merged').show()
+
+this.MergeRequest = MergeRequest
diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee
index ff843c68d68..9201c84c5ed 100644
--- a/app/assets/javascripts/merge_requests.js.coffee
+++ b/app/assets/javascripts/merge_requests.js.coffee
@@ -6,99 +6,3 @@
$('#milestone_id').select2()
$('#milestone_id, #assignee_id').on 'change', ->
$(this).closest('form').submit()
-
-class MergeRequest
-
- constructor: (@opts) ->
- this.$el = $('.merge-request')
- @diffs_loaded = if @opts.action == 'diffs' then true else false
- @commits_loaded = false
-
- this.activateTab(@opts.action)
-
- this.bindEvents()
-
- this.initMergeWidget()
- this.$('.show-all-commits').on 'click', =>
- this.showAllCommits()
-
- modal = $('#modal_merge_info').modal(show: false)
-
- disableButtonIfEmptyField '#merge_commit_message', '.accept_merge_request'
-
- # Local jQuery finder
- $: (selector) ->
- this.$el.find(selector)
-
- initMergeWidget: ->
- this.showState( @opts.current_status )
-
- if this.$('.automerge_widget').length and @opts.check_enable
- $.get @opts.url_to_automerge_check, (data) =>
- this.showState( data.merge_status )
- , 'json'
-
- if @opts.ci_enable
- $.get @opts.url_to_ci_check, (data) =>
- this.showCiState data.status
- , 'json'
-
- bindEvents: ->
- this.$('.nav-tabs').on 'click', 'a', (event) =>
- a = $(event.currentTarget)
-
- href = a.attr('href')
- History.replaceState {path: href}, document.title, href
-
- event.preventDefault()
-
- this.$('.nav-tabs').on 'click', 'li', (event) =>
- this.activateTab($(event.currentTarget).data('action'))
-
- this.$('.accept_merge_request').on 'click', ->
- $('.automerge_widget.can_be_merged').hide()
- $('.merge-in-progress').show()
-
- activateTab: (action) ->
- this.$('.nav-tabs li').removeClass 'active'
- this.$('.tab-content').hide()
- switch action
- when 'diffs'
- this.$('.nav-tabs .diffs-tab').addClass 'active'
- this.loadDiff() unless @diffs_loaded
- this.$('.diffs').show()
- else
- this.$('.nav-tabs .notes-tab').addClass 'active'
- this.$('.notes').show()
-
- showState: (state) ->
- $('.automerge_widget').hide()
- $('.automerge_widget.' + state).show()
-
- showCiState: (state) ->
- $('.ci_widget').hide()
- $('.ci_widget.ci-' + state).show()
-
- loadDiff: (event) ->
- $.ajax
- type: 'GET'
- url: this.$('.nav-tabs .diffs-tab a').attr('href')
- beforeSend: =>
- this.$('.status').addClass 'loading'
- complete: =>
- @diffs_loaded = true
- this.$('.status').removeClass 'loading'
- success: (data) =>
- this.$(".diffs").html(data.html)
- dataType: 'json'
-
- showAllCommits: ->
- this.$('.first-commits').remove()
- this.$('.all-commits').removeClass 'hide'
-
- alreadyOrCannotBeMerged: ->
- this.$('.automerge_widget').hide()
- this.$('.merge-in-progress').hide()
- this.$('.automerge_widget.already_cannot_be_merged').show()
-
-this.MergeRequest = MergeRequest
diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee
new file mode 100644
index 00000000000..03fad41c490
--- /dev/null
+++ b/app/assets/javascripts/project_users_select.js.coffee
@@ -0,0 +1,63 @@
+@projectUsersSelect =
+ init: ->
+ $('.ajax-project-users-select').each (i, select) ->
+ project_id = $('body').data('project-id')
+
+ $(select).select2
+ placeholder: $(select).data('placeholder') || "Search for a user"
+ multiple: $(select).hasClass('multiselect')
+ minimumInputLength: 0
+ query: (query) ->
+ Api.projectUsers project_id, query.term, (users) ->
+ data = { results: users }
+
+ nullUser = {
+ name: 'Unassigned',
+ avatar: null,
+ username: 'none',
+ id: ''
+ }
+
+ data.results.unshift(nullUser)
+
+ query.callback(data)
+
+ initSelection: (element, callback) ->
+ id = $(element).val()
+ if id isnt ""
+ Api.user(id, callback)
+
+
+ formatResult: projectUsersSelect.projectUserFormatResult
+ formatSelection: projectUsersSelect.projectUserFormatSelection
+ dropdownCssClass: "ajax-project-users-dropdown"
+ dropdownAutoWidth: true
+ escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
+ m
+
+ projectUserFormatResult: (user) ->
+ if user.avatar_url
+ avatar = user.avatar_url
+ else if gon.gravatar_enabled
+ avatar = gon.gravatar_url
+ avatar = avatar.replace('%{hash}', md5(user.email))
+ avatar = avatar.replace('%{size}', '24')
+ else
+ avatar = gon.relative_url_root + "/assets/no_avatar.png"
+
+ if user.id == ''
+ avatarMarkup = ''
+ else
+ avatarMarkup = "<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>"
+
+ "<div class='user-result'>
+ #{avatarMarkup}
+ <div class='user-name'>#{user.name}</div>
+ <div class='user-username'>#{user.username}</div>
+ </div>"
+
+ projectUserFormatSelection: (user) ->
+ user.name
+
+$ ->
+ projectUsersSelect.init()
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index c1fa16ca89c..ce9a505b1e3 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -1,7 +1,7 @@
$ ->
userFormatResult = (user) ->
- if user.avatar
- avatar = user.avatar.url
+ if user.avatar_url
+ avatar = user.avatar_url
else if gon.gravatar_enabled
avatar = gon.gravatar_url
avatar = avatar.replace('%{hash}', md5(user.email))
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index cc5fdf61405..4b7103010bb 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -7,6 +7,8 @@
*= require select2
*= require highlightjs.min
*= require_self
+ *= require nprogress
+ *= require nprogress-bootstrap
*/
@import "main/variables.scss";
@@ -37,6 +39,7 @@
@import "generic/issue_box.scss";
@import "generic/files.scss";
@import "generic/lists.scss";
+@import "generic/flash.scss";
@import "generic/forms.scss";
@import "generic/selects.scss";
@import "generic/highlight.scss";
@@ -47,6 +50,7 @@
@import "sections/header.scss";
@import "sections/nav.scss";
@import "sections/commits.scss";
+@import "sections/diff.scss";
@import "sections/issues.scss";
@import "sections/projects.scss";
@import "sections/snippets.scss";
@@ -65,9 +69,10 @@
@import "sections/wall.scss";
@import "sections/dashboard.scss";
@import "sections/stat_graph.scss";
+@import "sections/groups.scss";
/**
- * Code ighlight
+ * Code highlight
*/
@import "highlight/white.scss";
@import "highlight/dark.scss";
diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/generic/blocks.scss
index 1cbd7439835..3536a68f416 100644
--- a/app/assets/stylesheets/generic/blocks.scss
+++ b/app/assets/stylesheets/generic/blocks.scss
@@ -2,3 +2,18 @@
background: #f9f9f9;
padding: 15px;
}
+
+.centered-light-block {
+ text-align: center;
+ color: #888;
+ margin: 20px;
+}
+
+.nothing-here-block {
+ text-align: center;
+ padding: 20px;
+ color: #666;
+ font-weight: normal;
+ font-size: 16px;
+ line-height: 36px;
+}
diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss
index 219e6ebd68b..dd0a2938cfe 100644
--- a/app/assets/stylesheets/generic/buttons.scss
+++ b/app/assets/stylesheets/generic/buttons.scss
@@ -118,7 +118,6 @@
@extend .btn-primary;
}
- &.btn-close,
&.btn-remove {
@extend .btn-danger;
}
@@ -143,6 +142,30 @@
line-height: 16px;
margin: 2px;
}
+
+ &.btn-close {
+ color: #B94A48;
+ font-weight: bold;
+ &:hover {
+ color: #B94A48;
+ }
+ }
+
+ &.btn-reopen {
+ color: #468847;
+ font-weight: bold;
+ &:hover {
+ color: #468847;
+ }
+ }
+
+ &.btn-grouped {
+ margin-right: 7px;
+ float: left;
+ &:last-child {
+ margin-right: 0px;
+ }
+ }
}
.btn-block {
@@ -154,9 +177,8 @@
}
}
-.btn,
.btn-group {
- &.grouped {
+ &.btn-grouped {
margin-right: 7px;
float: left;
&:last-child {
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index 4824194ec42..4e660d0b1e0 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -11,8 +11,6 @@
.bgred { background: #F2DEDE!important }
/** COMMON CLASSES **/
-.left { float:left }
-
.prepend-top-10 { margin-top:10px }
.prepend-top-20 { margin-top:20px }
.prepend-left-10 { margin-left:10px }
@@ -24,32 +22,9 @@
.append-bottom-20 { margin-bottom:20px }
.inline { display: inline-block }
-.padded { padding:20px }
-.ipadded { padding:20px!important }
-.lborder { border-left:1px solid #eee }
-.underlined_link { text-decoration: underline; }
+.underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: #999; }
.light { color: #888 }
-.tiny { font-weight: normal }
-.vtop { vertical-align: top !important; }
-
-
-/** ALERT MESSAGES **/
-.alert.alert-disabled {
- background: #EEE;
- color: #777;
- border-color: #DDD;
-}
-
-/** HELPERS **/
-.nothing_here_message {
- text-align: center;
- padding: 20px;
- color: #666;
- font-weight: normal;
- font-size: 16px;
- line-height: 36px;
-}
.slead {
color: #666;
@@ -59,49 +34,23 @@
line-height: 24px;
}
-
.tab-content {
overflow: visible;
}
-@media (max-width: 1200px) {
- .only-wide {
- display: none;
- }
-}
-
-pre.well-pre {
- border: 1px solid #EEE;
- background: #f9f9f9;
- border-radius: 0;
- color: #555;
-}
-
-.input-append .btn.active, .input-prepend .btn.active {
- background: #CCC;
- border-color: #BBB;
- text-shadow: 0 1px 1px #fff;
- font-weight: bold;
- @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15));
-}
-
-/** Big Labels **/
-.state-label {
- font-size: 14px;
- padding: 6px 25px;
- text-align: center;
- @include border-radius(4px);
- text-shadow: none;
- margin-left: 10px;
-
- &.state-label-green {
- background: #4A4;
- color: #FFF;
+pre {
+ &.clean {
+ background: none;
+ border: none;
+ margin: 0;
+ padding: 0;
}
- &.state-label-red {
- background: #DA4E49;
- color: #FFF;
+ &.well-pre {
+ border: 1px solid #EEE;
+ background: #f9f9f9;
+ border-radius: 0;
+ color: #555;
}
}
@@ -112,6 +61,7 @@ pre.well-pre {
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background: #29b;
+ color: #FFF
}
.breadcrumb > li + li:before {
@@ -130,31 +80,6 @@ pre.well-pre {
}
/** FLASH message **/
-.flash-container {
- display: none;
- cursor: pointer;
- margin: 0;
- text-align: center;
- color: #fff;
- font-size: 14px;
- position: fixed;
- bottom: 0;
- width: 100%;
- opacity: 0.8;
- z-index: 100;
-
- .flash-notice {
- background: #49C;
- padding: 10px;
- text-shadow: 0 1px 1px #178;
- }
-
- .flash-alert {
- background: #C67;
- text-shadow: 0 1px 1px #945;
- padding: 10px;
- }
-}
.author_link {
color: $link_color;
}
@@ -274,22 +199,6 @@ li.note {
cursor: pointer;
}
-.merge-request,
-.issue {
- &.today{
- background: #EFE;
- border-color: #CEC;
- }
- &.closed {
- background: #F5f5f5;
- border-color: #E5E5E5;
- }
- &.merged {
- background: #F5f5f5;
- border-color: #E5E5E5;
- }
-}
-
.git_error_tips {
@extend .col-md-6;
text-align: left;
@@ -353,15 +262,6 @@ li.note {
}
}
-pre {
- &.clean {
- background: none;
- border: none;
- margin: 0;
- padding: 0;
- }
-}
-
.milestone {
&.milestone-closed {
background: #eee;
@@ -398,10 +298,6 @@ img.emoji {
width: 20px;
}
-.appear-data {
- display: none;
-}
-
.chart {
overflow: hidden;
height: 220px;
@@ -445,40 +341,6 @@ table {
margin-bottom: 20px;
}
-.ajax-users-select {
- width: 400px;
-
- &.input-large {
- width: 210px;
- }
-
- &.input-clamp {
- max-width: 100%;
- }
-}
-
-.user-result {
- .user-image {
- float: left;
- }
- .user-name {
- }
- .user-username {
- color: #999;
- }
-}
-
-.namespace-result {
- .namespace-kind {
- color: #AAA;
- font-weight: normal;
- }
- .namespace-path {
- margin-left: 10px;
- font-weight: bolder;
- }
-}
-
.btn-sign-in {
margin-top: 7px;
text-shadow: none;
@@ -493,3 +355,7 @@ table {
@media (max-width: $screen-xs-max) {
.container .content { margin-top: 20px; }
}
+
+.wiki .highlight, .note-body .highlight {
+ margin-bottom: 9px;
+}
diff --git a/app/assets/stylesheets/generic/flash.scss b/app/assets/stylesheets/generic/flash.scss
new file mode 100644
index 00000000000..95d28aaef6c
--- /dev/null
+++ b/app/assets/stylesheets/generic/flash.scss
@@ -0,0 +1,25 @@
+.flash-container {
+ display: none;
+ cursor: pointer;
+ margin: 0;
+ text-align: center;
+ color: #fff;
+ font-size: 14px;
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ opacity: 0.8;
+ z-index: 100;
+
+ .flash-notice {
+ background: #49C;
+ padding: 10px;
+ text-shadow: 0 1px 1px #178;
+ }
+
+ .flash-alert {
+ background: #C67;
+ text-shadow: 0 1px 1px #945;
+ padding: 10px;
+ }
+}
diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss
index 931b75a3234..56cd4db905e 100644
--- a/app/assets/stylesheets/generic/forms.scss
+++ b/app/assets/stylesheets/generic/forms.scss
@@ -1,5 +1,5 @@
input[type='search'].search-text-input {
- background-image: url("icon-search.png");
+ background-image: image-url("icon-search.png");
background-repeat: no-repeat;
background-position: 10px;
padding-left: 25px;
@@ -51,3 +51,27 @@ label {
.input-mn-300 {
min-width: 300px;
}
+
+.custom-form-control {
+ width: 150px;
+}
+
+@media (min-width: $screen-sm-min) {
+ .custom-form-control {
+ width: 150px;
+ }
+}
+
+/* Medium devices (desktops, 992px and up) */
+@media (min-width: $screen-md-min) {
+ .custom-form-control {
+ width: 170px;
+ }
+}
+
+/* Large devices (large desktops, 1200px and up) */
+@media (min-width: $screen-lg-min) {
+ .custom-form-control {
+ width: 200px;
+ }
+}
diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss
index 033b4b20f5b..3db4d908d9c 100644
--- a/app/assets/stylesheets/generic/issue_box.scss
+++ b/app/assets/stylesheets/generic/issue_box.scss
@@ -10,35 +10,103 @@
.issue-box {
color: #666;
margin:20px 0;
- background: #FAFAFA;
+ background: #FFF;
border: 1px solid #EEE;
+ &.issue-box-closed {
+ border-color: #DA4E49;
+ .state {
+ background-color: #f2dede;
+ border-color: #ebccd1;
+ color: #a94442;
+ .state-label {
+ background: #DA4E49;
+ color: #FFF;
+ }
+ }
+ }
+
+ &.issue-box-merged {
+ border-color: #31708f;
+ .state {
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+ color: #31708f;
+ .state-label {
+ background: #31708f;
+ color: #FFF;
+ }
+ }
+ }
+
+ &.issue-box-open {
+ border-color: #4A4;
+ .state {
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+ color: #3c763d;
+ .state-label {
+ background: #4A4;
+ color: #FFF;
+ }
+ }
+ }
+
+ &.issue-box-expired {
+ border-color: #cea61b;
+ .state {
+ background-color: #fcf8e3;
+ border-color: #faebcc;
+ color: #8a6d3b;
+ .state-label {
+ background: #cea61b;
+ color: #FFF;
+ }
+ }
+ }
+
.control-group {
margin-bottom: 0;
}
+ .state {
+ height: 34px;
+ border-bottom: 1px solid #DDD;
+ line-height: 32px;
+ }
+
.title {
font-size: 22px;
font-weight: 500;
line-height: 1.5;
margin: 0;
color: #333;
+ padding-bottom: 0;
+ padding: 15px 25px;
}
.context {
border: none;
border-top: 1px solid #eee;
+ padding: 15px 25px;
}
.description {
- border-top: 1px solid #eee;
+ padding: 0 25px 15px 25px;
}
.title, .context, .description {
- padding: 15px 25px;
-
.clearfix {
margin: 0;
}
}
+
+ .state-label {
+ font-size: 14px;
+ padding: 1px 25px;
+ text-align: center;
+ text-shadow: none;
+ margin-right: 20px;
+ display: inline-block;
+ }
}
diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss
index de70e47333f..963768044d5 100644
--- a/app/assets/stylesheets/generic/lists.scss
+++ b/app/assets/stylesheets/generic/lists.scss
@@ -13,6 +13,12 @@
border-bottom: 1px solid #eee;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+ &:after {
+ content: " ";
+ display: table;
+ clear: both;
+ }
+
&.disabled {
color: #888;
}
@@ -46,6 +52,12 @@
.author { color: #999; }
+ .list-item-name {
+ float: left;
+ position: relative;
+ top: 3px;
+ }
+
p {
padding-top: 1px;
margin: 0;
diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss
index c506bff8a74..03597c1fc7d 100644
--- a/app/assets/stylesheets/generic/selects.scss
+++ b/app/assets/stylesheets/generic/selects.scss
@@ -1,5 +1,4 @@
/** Select2 selectbox style override **/
-
.select2-container, .select2-container.select2-drop-above {
.select2-choice {
background: #FFF;
@@ -12,9 +11,13 @@
}
.select2-drop-active {
- border: 1px solid #BBB;
+ border: 1px solid #BBB !important;
margin-top: 4px;
+ &.select2-drop-above {
+ margin-bottom: 8px;
+ }
+
.select2-search input {
background: #fafafa;
border-color: #DDD;
@@ -78,3 +81,43 @@ select {
.project-refs-form .select2-container {
margin-right: 10px;
}
+
+.ajax-users-dropdown, .ajax-project-users-dropdown {
+ .select2-search {
+ padding-top: 4px;
+ }
+}
+
+.ajax-users-select {
+ width: 400px;
+
+ &.input-large {
+ width: 210px;
+ }
+
+ &.input-clamp {
+ max-width: 100%;
+ }
+}
+
+.user-result {
+ .user-image {
+ float: left;
+ }
+ .user-name {
+ }
+ .user-username {
+ color: #999;
+ }
+}
+
+.namespace-result {
+ .namespace-kind {
+ color: #AAA;
+ font-weight: normal;
+ }
+ .namespace-path {
+ margin-left: 10px;
+ font-weight: bolder;
+ }
+}
diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
index 1a07fde53f1..a4419551738 100644
--- a/app/assets/stylesheets/generic/typography.scss
+++ b/app/assets/stylesheets/generic/typography.scss
@@ -88,9 +88,6 @@ a:focus {
.wiki {
@include md-typography;
- font-size: 14px;
- line-height: 1.6;
-
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
@@ -105,7 +102,7 @@ a:focus {
display: inline-block;
width: $size;
height: $size;
- background-image: url("icon-link.png");
+ background-image: image-url("icon-link.png");
background-size: contain;
background-repeat: no-repeat;
}
@@ -120,3 +117,11 @@ a:focus {
.md {
@include md-typography;
}
+
+/**
+ * Textareas intended for GFM
+ *
+ */
+textarea.js-gfm-input {
+ font-family: $monospace_font;
+}
diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/gl_bootstrap.scss
index 7f45d64fb7c..169f1268cd7 100644
--- a/app/assets/stylesheets/gl_bootstrap.scss
+++ b/app/assets/stylesheets/gl_bootstrap.scss
@@ -108,6 +108,8 @@ $pagination-active-bg: $bg_style_color;
// Nav tabs
.nav.nav-tabs {
+ margin-bottom: 15px;
+
li {
> a {
padding: 8px 20px;
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 880387a3483..c06bed3c21e 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -178,3 +178,9 @@
.shadow {
@include box-shadow(0 5px 15px #000);
}
+
+.wiki, .note-body {
+ .highlight {
+ border: 1px solid #DDD;
+ }
+}
diff --git a/app/assets/stylesheets/main/layout.scss b/app/assets/stylesheets/main/layout.scss
index 9e009a5e0ad..e28da65c01f 100644
--- a/app/assets/stylesheets/main/layout.scss
+++ b/app/assets/stylesheets/main/layout.scss
@@ -5,9 +5,7 @@ html {
}
body {
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- margin-bottom: 20px;
+ padding-bottom: 20px;
}
.container {
diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/main/mixins.scss
index 4afe61d756c..fcc7374d0d9 100644
--- a/app/assets/stylesheets/main/mixins.scss
+++ b/app/assets/stylesheets/main/mixins.scss
@@ -84,6 +84,9 @@
}
@mixin md-typography {
+ font-size: 14px;
+ line-height: 1.6;
+
img {
max-width: 100%;
}
diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss
index decc5f50469..4b5fa0979be 100644
--- a/app/assets/stylesheets/main/variables.scss
+++ b/app/assets/stylesheets/main/variables.scss
@@ -5,6 +5,7 @@ $primary_color: #2FA0BB;
$link_color: #3A89A3;
$style_color: #474D57;
$bg_style_color: #2299BB;
+$list-group-active-bg: $bg_style_color;
$hover: #D9EDF7;
/**
diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss
new file mode 100644
index 00000000000..42dbf4d6ef3
--- /dev/null
+++ b/app/assets/stylesheets/print.scss
@@ -0,0 +1,13 @@
+/* Generic print styles */
+header, nav, nav.main-nav, nav.navbar-collapse, nav.navbar-collapse.collapse {display: none!important;}
+.profiler-results {display: none;}
+
+/* Styles targeted specifically at printing files */
+.tree-ref-holder, .tree-holder .breadcrumb, .blob-commit-info {display: none;}
+.file-title {display: none;}
+.file-holder {border: none;}
+
+.wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; }
+.wiki h1 {font-size: 30px;}
+.wiki h2 {font-size: 22px;}
+.wiki h3 {font-size: 18px; font-weight: bold; }
diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/sections/admin.scss
index 8ad9bc732b2..a558633d112 100644
--- a/app/assets/stylesheets/sections/admin.scss
+++ b/app/assets/stylesheets/sections/admin.scss
@@ -2,7 +2,7 @@
* Admin area
*
*/
-.admin_dash {
+.admin-dashboard {
.data {
a {
h1 {
@@ -14,6 +14,10 @@
}
}
}
+
+ .str-truncated {
+ max-width: 60%;
+ }
}
.admin-filter form {
diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss
index ff293bc4a00..855bb4ea010 100644
--- a/app/assets/stylesheets/sections/commits.scss
+++ b/app/assets/stylesheets/sections/commits.scss
@@ -10,331 +10,6 @@
}
}
-.file {
- border: 1px solid #CCC;
- margin-bottom: 1em;
-
- .header {
- @extend .clearfix;
- background: #DDD;
- border-bottom: 1px solid #CCC;
- padding: 5px 5px 5px 10px;
- color: #555;
-
- > span {
- font-family: $monospace_font;
- font-size: 14px;
- line-height: 2;
- }
-
- .view-file {
- font-weight: bold;
- float: right;
- background-color: #EEE;
- }
-
- .commit-short-id {
- font-family: $monospace_font;
- font-size: smaller;
- }
-
- .file-mode {
- font-family: $monospace_font;
- }
- }
- .content {
- overflow: auto;
- overflow-y: hidden;
- background: #FFF;
- color: #333;
- font-size: 12px;
- .old {
- span.idiff {
- background-color: #FAA;
- }
- }
- .new {
- span.idiff {
- background-color: #AFA;
- }
- }
-
- table {
- width: 100%;
- font-family: $monospace_font;
- border: none;
- margin: 0px;
- padding: 0px;
- td {
- line-height: 18px;
- font-size: 12px;
- }
- }
- .old_line, .new_line, .diff_line {
- margin: 0px;
- padding: 0px;
- border: none;
- background: #EEE;
- color: #666;
- padding: 0px 5px;
- border-right: 1px solid #ccc;
- text-align: right;
- min-width: 35px;
- max-width: 50px;
- width: 35px;
- @include user-select(none);
- a {
- float: left;
- width: 35px;
- font-weight: normal;
- color: #666;
- &:hover {
- text-decoration: underline;
- }
- }
- &.new {
- background: #CFD;
- }
- &.old {
- background: #FDD;
- }
- }
- .diff_line {
- padding: 0;
- }
- .line_holder {
- &.old .old_line,
- &.old .new_line {
- background: #FCC;
- border-color: #E7BABA;
- }
- &.new .old_line,
- &.new .new_line {
- background: #CFC;
- border-color: #B9ECB9;
- }
- }
- .line_content {
- display: block;
- white-space: pre;
- height: 18px;
- margin: 0px;
- padding: 0px 0.5em;
- border: none;
- &.new {
- background: #CFD;
- }
- &.old {
- background: #FDD;
- }
- &.matched {
- color: #ccc;
- background: #fafafa;
- }
- &.parallel {
- display: table-cell;
- overflow: hidden;
- width: 50%;
- }
- }
- }
- .image {
- background: #ddd;
- text-align: center;
- padding: 30px;
- .wrap{
- display: inline-block;
- }
-
- .frame {
- display: inline-block;
- background-color: #fff;
- line-height: 0;
- img{
- border: 1px solid #FFF;
- background: url('trans_bg.gif');
- }
- &.deleted {
- border: 1px solid $deleted;
- }
-
- &.added {
- border: 1px solid $added;
- }
- }
- .image-info{
- font-size: 12px;
- margin: 5px 0 0 0;
- color: grey;
- }
-
- .view.swipe{
- position: relative;
-
- .swipe-frame{
- display: block;
- margin: auto;
- position: relative;
- }
- .swipe-wrap{
- overflow: hidden;
- border-left: 1px solid #999;
- position: absolute;
- display: block;
- top: 13px;
- right: 7px;
- }
- .frame{
- top: 0;
- right: 0;
- position: absolute;
- &.deleted{
- margin: 0;
- display: block;
- top: 13px;
- right: 7px;
- }
- }
- .swipe-bar{
- display: block;
- height: 100%;
- width: 15px;
- z-index: 100;
- position: absolute;
- cursor: pointer;
- &:hover{
- .top-handle{
- background-position: -15px 3px;
- }
- .bottom-handle{
- background-position: -15px -11px;
- }
- };
- .top-handle{
- display: block;
- height: 14px;
- width: 15px;
- position: absolute;
- top: 0px;
- background: url('swipemode_sprites.gif') 0 3px no-repeat;
- }
- .bottom-handle{
- display: block;
- height: 14px;
- width: 15px;
- position: absolute;
- bottom: 0px;
- background: url('swipemode_sprites.gif') 0 -11px no-repeat;
- }
- }
- } //.view.swipe
- .view.onion-skin{
- .onion-skin-frame{
- display: block;
- margin: auto;
- position: relative;
- }
- .frame.added, .frame.deleted {
- position: absolute;
- display: block;
- top: 0px;
- left: 0px;
- }
- .controls{
- display: block;
- height: 14px;
- width: 300px;
- z-index: 100;
- position: absolute;
- bottom: 0px;
- left: 50%;
- margin-left: -150px;
-
- .drag-track{
- display: block;
- position: absolute;
- left: 12px;
- height: 10px;
- width: 276px;
- background: url('onion_skin_sprites.gif') -4px -20px repeat-x;
- }
-
- .dragger {
- display: block;
- position: absolute;
- left: 0px;
- top: 0px;
- height: 14px;
- width: 14px;
- background: url('onion_skin_sprites.gif') 0px -34px repeat-x;
- cursor: pointer;
- }
-
- .transparent {
- display: block;
- position: absolute;
- top: 2px;
- right: 0px;
- height: 10px;
- width: 10px;
- background: url('onion_skin_sprites.gif') -2px 0px no-repeat;
- }
-
- .opaque {
- display: block;
- position: absolute;
- top: 2px;
- left: 0px;
- height: 10px;
- width: 10px;
- background: url('onion_skin_sprites.gif') -2px -10px no-repeat;
- }
- }
- } //.view.onion-skin
- }
- .view-modes{
-
- padding: 10px;
- text-align: center;
-
- background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
- background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
- background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
- background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf);
- background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
-
- ul, li{
- list-style: none;
- margin: 0;
- padding: 0;
- display: inline-block;
- }
-
- li{
- color: grey;
- border-left: 1px solid #c1c1c1;
- padding: 0 12px 0 16px;
- cursor: pointer;
- &:first-child{
- border-left: none;
- }
- &:hover{
- text-decoration: underline;
- }
- &.active{
- &:hover{
- text-decoration: none;
- }
- cursor: default;
- color: #333;
- }
- &.disabled{
- display: none;
- }
- }
- }
-}
-
/** COMMIT BLOCK **/
.commit-title{
display: block;
@@ -398,7 +73,7 @@
.commits-compare-switch{
- background: url("switch_icon.png") no-repeat center center;
+ background: image-url("switch_icon.png") no-repeat center center;
width: 32px;
height: 32px;
text-indent: -9999px;
@@ -493,6 +168,25 @@ li.commit {
text-decoration: underline;
}
}
+
+ .text-expander {
+ background: #eee;
+ color: #555;
+ padding: 0 5px;
+ cursor: pointer;
+ margin-left: 4px;
+ &:hover {
+ background-color: #ddd;
+ }
+ }
+ }
+
+ .commit-row-description {
+ font-size: 14px;
+ border-left: 1px solid #e5e5e5;
+ padding: 0 15px 0 7px;
+ margin: 5px 0 10px 5px;
+ display: none;
}
.commit-row-info {
diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss
index 1fd82c84fc9..e70757e0406 100644
--- a/app/assets/stylesheets/sections/dashboard.scss
+++ b/app/assets/stylesheets/sections/dashboard.scss
@@ -41,7 +41,7 @@
.dash-sidebar-tabs {
margin-bottom: 2px;
border: none;
- margin: 0;
+ margin: 0 !important;
li {
&.active {
@@ -61,7 +61,7 @@
}
.project-row, .group-row {
- padding: 10px 12px !important;
+ padding: 8px 12px !important;
font-size: 14px;
line-height: 24px;
@@ -113,6 +113,5 @@
float: left;
margin-right: 3px;
color: #999;
- margin-bottom: 10px;
width: 16px;
}
diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/sections/diff.scss
new file mode 100644
index 00000000000..eb272f20f40
--- /dev/null
+++ b/app/assets/stylesheets/sections/diff.scss
@@ -0,0 +1,350 @@
+.diff-file {
+ border: 1px solid #CCC;
+ margin-bottom: 1em;
+
+ .diff-header {
+ @extend .clearfix;
+ background: #DDD;
+ border-bottom: 1px solid #CCC;
+ padding: 5px 5px 5px 10px;
+ color: #555;
+
+ > span {
+ font-family: $monospace_font;
+ font-size: 14px;
+ line-height: 2;
+ }
+
+ .diff-btn-group {
+ float: right;
+
+ .btn {
+ background-color: #EEE;
+ color: #666;
+ font-weight: bolder;
+ }
+ }
+
+ .commit-short-id {
+ font-family: $monospace_font;
+ font-size: smaller;
+ }
+
+ .file-mode {
+ font-family: $monospace_font;
+ }
+ }
+ .diff-content {
+ overflow: auto;
+ overflow-y: hidden;
+ background: #FFF;
+ color: #333;
+ font-size: 12px;
+ .old {
+ span.idiff {
+ background-color: #FAA;
+ }
+ }
+ .new {
+ span.idiff {
+ background-color: #AFA;
+ }
+ }
+
+ table {
+ width: 100%;
+ font-family: $monospace_font;
+ border: none;
+ margin: 0px;
+ padding: 0px;
+ td {
+ line-height: 18px;
+ font-size: 12px;
+ }
+ }
+
+ .text-file-parallel div {
+ display: inline-block;
+ padding-bottom: 16px;
+ }
+ .diff-side {
+ overflow-x: scroll;
+ width: 508px;
+ height: 700px;
+ }
+ .diff-side.diff-side-left{
+ overflow-y:hidden;
+ }
+ .diff-side table, td.diff-middle table {
+ height: 700px;
+ }
+ .diff-middle {
+ width: 114px;
+ vertical-align: top;
+ height: 700px;
+ overflow: hidden
+ }
+
+ .old_line, .new_line, .diff_line {
+ margin: 0px;
+ padding: 0px;
+ border: none;
+ background: #EEE;
+ color: #666;
+ padding: 0px 5px;
+ border-right: 1px solid #ccc;
+ text-align: right;
+ min-width: 35px;
+ max-width: 50px;
+ width: 35px;
+ @include user-select(none);
+ a {
+ float: left;
+ width: 35px;
+ font-weight: normal;
+ color: #666;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ &.new {
+ background: #CFD;
+ }
+ &.old {
+ background: #FDD;
+ }
+ }
+ .diff_line {
+ padding: 0;
+ }
+ .line_holder {
+ &.old .old_line,
+ &.old .new_line {
+ background: #FCC;
+ border-color: #E7BABA;
+ }
+ &.new .old_line,
+ &.new .new_line {
+ background: #CFC;
+ border-color: #B9ECB9;
+ }
+ }
+ .line_content {
+ display: block;
+ white-space: pre;
+ height: 18px;
+ margin: 0px;
+ padding: 0px 0.5em;
+ border: none;
+ &.new {
+ background: #CFD;
+ }
+ &.old {
+ background: #FDD;
+ }
+ &.matched {
+ color: #ccc;
+ background: #fafafa;
+ }
+ &.parallel {
+ display: table-cell;
+ }
+ }
+ }
+ .image {
+ background: #ddd;
+ text-align: center;
+ padding: 30px;
+ .wrap{
+ display: inline-block;
+ }
+
+ .frame {
+ display: inline-block;
+ background-color: #fff;
+ line-height: 0;
+ img{
+ border: 1px solid #FFF;
+ background: image-url('trans_bg.gif');
+ max-width: 100%;
+ }
+ &.deleted {
+ border: 1px solid $deleted;
+ }
+
+ &.added {
+ border: 1px solid $added;
+ }
+ }
+ .image-info{
+ font-size: 12px;
+ margin: 5px 0 0 0;
+ color: grey;
+ }
+
+ .view.swipe{
+ position: relative;
+
+ .swipe-frame{
+ display: block;
+ margin: auto;
+ position: relative;
+ }
+ .swipe-wrap{
+ overflow: hidden;
+ border-left: 1px solid #999;
+ position: absolute;
+ display: block;
+ top: 13px;
+ right: 7px;
+ }
+ .frame{
+ top: 0;
+ right: 0;
+ position: absolute;
+ &.deleted{
+ margin: 0;
+ display: block;
+ top: 13px;
+ right: 7px;
+ }
+ }
+ .swipe-bar{
+ display: block;
+ height: 100%;
+ width: 15px;
+ z-index: 100;
+ position: absolute;
+ cursor: pointer;
+ &:hover{
+ .top-handle{
+ background-position: -15px 3px;
+ }
+ .bottom-handle{
+ background-position: -15px -11px;
+ }
+ };
+ .top-handle{
+ display: block;
+ height: 14px;
+ width: 15px;
+ position: absolute;
+ top: 0px;
+ background: image-url('swipemode_sprites.gif') 0 3px no-repeat;
+ }
+ .bottom-handle{
+ display: block;
+ height: 14px;
+ width: 15px;
+ position: absolute;
+ bottom: 0px;
+ background: image-url('swipemode_sprites.gif') 0 -11px no-repeat;
+ }
+ }
+ } //.view.swipe
+ .view.onion-skin{
+ .onion-skin-frame{
+ display: block;
+ margin: auto;
+ position: relative;
+ }
+ .frame.added, .frame.deleted {
+ position: absolute;
+ display: block;
+ top: 0px;
+ left: 0px;
+ }
+ .controls{
+ display: block;
+ height: 14px;
+ width: 300px;
+ z-index: 100;
+ position: absolute;
+ bottom: 0px;
+ left: 50%;
+ margin-left: -150px;
+
+ .drag-track{
+ display: block;
+ position: absolute;
+ left: 12px;
+ height: 10px;
+ width: 276px;
+ background: image-url('onion_skin_sprites.gif') -4px -20px repeat-x;
+ }
+
+ .dragger {
+ display: block;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ height: 14px;
+ width: 14px;
+ background: image-url('onion_skin_sprites.gif') 0px -34px repeat-x;
+ cursor: pointer;
+ }
+
+ .transparent {
+ display: block;
+ position: absolute;
+ top: 2px;
+ right: 0px;
+ height: 10px;
+ width: 10px;
+ background: image-url('onion_skin_sprites.gif') -2px 0px no-repeat;
+ }
+
+ .opaque {
+ display: block;
+ position: absolute;
+ top: 2px;
+ left: 0px;
+ height: 10px;
+ width: 10px;
+ background: image-url('onion_skin_sprites.gif') -2px -10px no-repeat;
+ }
+ }
+ } //.view.onion-skin
+ }
+ .view-modes{
+
+ padding: 10px;
+ text-align: center;
+
+ background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
+ background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
+ background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
+ background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf);
+ background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
+
+ ul, li{
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ }
+
+ li{
+ color: grey;
+ border-left: 1px solid #c1c1c1;
+ padding: 0 12px 0 16px;
+ cursor: pointer;
+ &:first-child{
+ border-left: none;
+ }
+ &:hover{
+ text-decoration: underline;
+ }
+ &.active{
+ &:hover{
+ text-decoration: none;
+ }
+ cursor: default;
+ color: #333;
+ }
+ &.disabled{
+ display: none;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss
index 801233519b6..b68f882220e 100644
--- a/app/assets/stylesheets/sections/events.scss
+++ b/app/assets/stylesheets/sections/events.scss
@@ -37,12 +37,12 @@
&.event-inline {
.avatar {
- width: 16px;
- height: 16px;
+ position: relative;
+ top: -2px;
}
}
- padding: 14px 0px;
+ padding: 12px 0px;
border-bottom: 1px solid #eee;
.event-title {
color: #333;
@@ -63,6 +63,10 @@
color: #666;
margin-top: 5px;
+ .md {
+ font-size: 13px;
+ }
+
pre {
border: none;
background: #f9f9f9;
diff --git a/app/assets/stylesheets/sections/groups.scss b/app/assets/stylesheets/sections/groups.scss
new file mode 100644
index 00000000000..60ec79acadb
--- /dev/null
+++ b/app/assets/stylesheets/sections/groups.scss
@@ -0,0 +1,9 @@
+.new-group-member-holder {
+ margin-top: 50px;
+ background: #f9f9f9;
+ padding-top: 20px;
+}
+
+.member-search-form {
+ float: left;
+}
diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss
index c8091c84891..06709bd7ef6 100644
--- a/app/assets/stylesheets/sections/header.scss
+++ b/app/assets/stylesheets/sections/header.scss
@@ -19,7 +19,7 @@ header {
line-height: 32px;
padding: 6px 10px;
- &:hover {
+ &:hover, &:focus, &:active {
background: none;
}
}
@@ -161,7 +161,7 @@ header {
}
.search-input {
- background-image: url("icon-search.png");
+ background-image: image-url("icon-search.png");
background-repeat: no-repeat;
background-position: 10px;
height: inherit;
@@ -192,7 +192,8 @@ header {
color: #AAA;
text-shadow: 0 1px 0 #444;
- &:hover {
+ &:hover, &:focus, &:active {
+ background: none;
color: #FFF;
}
}
@@ -229,9 +230,9 @@ header {
}
.title {
a {
- color: #BBB;
+ color: #FFF;
&:hover {
- color: #FFF;
+ text-decoration: underline;
}
}
color: #fff;
@@ -272,3 +273,9 @@ header {
}
}
}
+
+@media (max-width: $screen-xs-max) {
+ #nprogress .spinner {
+ right: 35px !important;
+ }
+}
diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss
index 5afb13a86ba..d4f8c8108ab 100644
--- a/app/assets/stylesheets/sections/issues.scss
+++ b/app/assets/stylesheets/sections/issues.scss
@@ -14,8 +14,8 @@
.issue-check {
float: left;
- padding: 8px 0;
padding-right: 8px;
+ margin-bottom: 10px;
min-width: 15px;
}
@@ -38,13 +38,21 @@
}
}
-input.check_all_issues {
+.check-all-holder {
+ height: 32px;
float: left;
- padding: 0;
- margin: 0;
- margin-right: 10px;
- position: relative;
- top: 13px;
+ margin-right: 12px;
+ padding: 6px 10px;
+ border: 1px solid #ccc;
+ @include border-radius(4px);
+
+
+ input.check_all_issues {
+ padding: 0;
+ margin: 0;
+ position: relative;
+ top: 3px;
+ }
}
.issues_content {
@@ -57,21 +65,6 @@ input.check_all_issues {
}
}
-.btn.close_issue {
- color: #B94A48;
- font-weight: bold;
- &:hover {
- color: #B94A48;
- }
-}
-.btn.reopen_issue {
- color: #468847;
- font-weight: bold;
- &:hover {
- color: #468847;
- }
-}
-
@media (min-width: 800px) { .issues_filters select { width: 160px; } }
@media (min-width: 1200px) { .issues_filters select { width: 220px; } }
@@ -91,6 +84,13 @@ input.check_all_issues {
.update_selected_issues {
margin-left: 4px;
}
+
+ .select2-container .select2-choice {
+ height: 32px;
+ line-height: 28px;
+ color: #444 !important;
+ font-weight: 500;
+ }
}
}
@@ -125,3 +125,21 @@ input.check_all_issues {
form.edit-issue {
margin: 0;
}
+
+.merge-request,
+.issue {
+ &.today {
+ background: #EFE;
+ border-color: #CEC;
+ }
+
+ &.closed {
+ background: #F5f5f5;
+ border-color: #E5E5E5;
+ }
+
+ &.merged {
+ background: #F5f5f5;
+ border-color: #E5E5E5;
+ }
+}
diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss
index 4388da00735..5341f411e54 100644
--- a/app/assets/stylesheets/sections/merge_requests.scss
+++ b/app/assets/stylesheets/sections/merge_requests.scss
@@ -31,10 +31,10 @@
.mr_source_commit,
.mr_target_commit {
+ margin-top: 10px;
.commit {
margin: 0;
- padding: 0;
- padding: 5px 0;
+ padding: 2px 0;
list-style: none;
&:hover {
background: none;
@@ -89,3 +89,9 @@
.merge-request-form-info {
padding-top: 15px;
}
+
+// hide mr close link for inline diff comment form
+.diff-file .close-mr-link,
+.diff-file .reopen-mr-link {
+ display: none;
+}
diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss
index 372fa9669ca..88f16a21596 100644
--- a/app/assets/stylesheets/sections/nav.scss
+++ b/app/assets/stylesheets/sections/nav.scss
@@ -36,8 +36,7 @@
&.active {
a {
color: #333;
- font-weight: bolder;
-
+ font-weight: bold;
&:after {
content: '';
display: block;
@@ -56,7 +55,20 @@
&:hover {
a {
- color: $style_color;
+ color: $link_color;
+ &:after {
+ content: '';
+ display: block;
+ position: relative;
+ bottom: 8px;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border-color: transparent transparent #29b transparent;
+ border-style: solid;
+ border-width: 6px;
+ margin-left: -6px;
+ }
}
}
@@ -73,7 +85,7 @@
a {
display: block;
text-align: center;
- font-weight: normal;
+ font-weight: 500;
height: 38px;
line-height: 34px;
color: #777;
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss
index 9f5f1579fbd..4d7a873a519 100644
--- a/app/assets/stylesheets/sections/notes.scss
+++ b/app/assets/stylesheets/sections/notes.scss
@@ -27,11 +27,15 @@ ul.notes {
.discussion-last-update,
.note-last-update {
- font-style: italic;
+ &:before {
+ content: "\00b7";
+ }
+ font-size: 13px;
}
.author {
- color: $style_color;
+ color: #555;
font-weight: bold;
+ font-size: 14px;
&:hover {
color: $primary_color;
}
@@ -47,13 +51,13 @@ ul.notes {
.discussion-body {
margin-left: 50px;
- .file,
+ .diff-file,
.discussion-hidden,
.notes {
@extend .borders;
background-color: #F9F9F9;
}
- .file .notes {
+ .diff-file .notes {
/* reset */
background: inherit;
border: none;
@@ -87,7 +91,6 @@ ul.notes {
}
.attachment {
font-size: 14px;
- margin-top: -20px;
}
.note-body {
@include md-typography;
@@ -114,7 +117,7 @@ ul.notes {
}
}
-.file .notes_holder {
+.diff-file .notes_holder {
font-size: 13px;
line-height: 18px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
@@ -184,20 +187,19 @@ ul.notes {
}
}
}
-.file .note .note-actions {
+.diff-file .note .note-actions {
right: 0;
top: 0;
}
-
/**
* Line note button on the side of diffs
*/
-.file tr.line_holder {
+.diff-file tr.line_holder {
.add-diff-note {
- background: url("diff_note_add.png") no-repeat left 0;
+ background: image-url("diff_note_add.png") no-repeat left 0;
height: 22px;
margin-left: -65px;
position: absolute;
@@ -235,22 +237,25 @@ ul.notes {
.reply-btn {
@extend .btn-primary;
}
-.file .content tr.line_holder:hover {
- &> td.line_content {
- background: $hover !important;
- border-color: darken($hover, 10%) !important;
+.diff-file .diff-content {
+ tr.line_holder:hover {
+ &> td.line_content {
+ background: $hover !important;
+ border-color: darken($hover, 10%) !important;
+ }
+ &> td.new_line,
+ &> td.old_line {
+ background: darken($hover, 4%) !important;
+ border-color: darken($hover, 10%) !important;
+ }
}
- &> td.new_line,
- &> td.old_line {
- background: darken($hover, 4%) !important;
- border-color: darken($hover, 10%) !important;
+
+ tr.line_holder:hover > td .line_note_link {
+ opacity: 1.0;
+ filter: alpha(opacity=100);
}
}
-.file .content tr.line_holder:hover > td .line_note_link {
- opacity: 1.0;
- filter: alpha(opacity=100);
-}
-.file,
+.diff-file,
.discussion {
.new_note {
margin: 0;
@@ -302,6 +307,7 @@ ul.notes {
@extend .col-md-4;
@extend .thumbnail;
margin-left: 45px;
+ float: none;
}
diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss
index 0ee46b9a2f7..7a696c21e47 100644
--- a/app/assets/stylesheets/sections/profile.scss
+++ b/app/assets/stylesheets/sections/profile.scss
@@ -114,3 +114,14 @@
height: 50px;
}
}
+
+.global-notifications-form .level-title {
+ font-size: 15px;
+ color: #333;
+ font-weight: bold;
+}
+
+.notification-icon-holder {
+ width: 20px;
+ float: left;
+}
diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss
index f72c6607181..55a5819b55a 100644
--- a/app/assets/stylesheets/sections/tree.scss
+++ b/app/assets/stylesheets/sections/tree.scss
@@ -127,7 +127,7 @@
border-top: 1px dashed #CCC;
padding-top: 10px;
- h4 {
+ .readme-file-title {
font-size: 14px;
margin-bottom: 20px;
color: #777;
diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss
index 0fc72d4e0a8..edac4290e74 100644
--- a/app/assets/stylesheets/themes/ui_color.scss
+++ b/app/assets/stylesheets/themes/ui_color.scss
@@ -36,4 +36,8 @@
}
}
}
+
+ .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
+ background: #769;
+ }
}
diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb
index 159ab2027c2..4c1d0df4110 100644
--- a/app/controllers/admin/background_jobs_controller.rb
+++ b/app/controllers/admin/background_jobs_controller.rb
@@ -1,5 +1,6 @@
class Admin::BackgroundJobsController < Admin::ApplicationController
def show
- @sidekiq_processes = `ps -U #{Settings.gitlab.user} -o euser,pid,pcpu,pmem,stat,start,command | grep sidekiq | grep -v grep`
+ ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
+ @sidekiq_processes = ps_output.split("\n").grep(/sidekiq/)
end
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index bdbb9a354b4..5b06af79d5a 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -68,7 +68,9 @@ class Admin::UsersController < Admin::ApplicationController
params[:user].delete(:password_confirmation)
end
- user.admin = (admin && admin.to_i > 0)
+ if admin.present?
+ user.admin = !admin.to_i.zero?
+ end
respond_to do |format|
if user.update_attributes(params[:user], as: :admin)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index acb2f2c21d8..5f8b2da06f8 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
before_filter :check_password_expiration
around_filter :set_current_user_for_thread
before_filter :add_abilities
+ before_filter :ldap_security_check
before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers
before_filter :add_gon_variables
@@ -154,7 +155,6 @@ class ApplicationController < ActionController::Base
end
def dev_tools
- Rack::MiniProfiler.authorize_request
end
def default_headers
@@ -180,11 +180,30 @@ class ApplicationController < ActionController::Base
end
end
+ def ldap_security_check
+ if current_user && current_user.requires_ldap_check?
+ gitlab_ldap_access do |access|
+ if access.allowed?(current_user)
+ current_user.last_credential_check_at = Time.now
+ current_user.save
+ else
+ sign_out current_user
+ flash[:alert] = "Access denied for your LDAP account."
+ redirect_to new_user_session_path
+ end
+ end
+ end
+ end
+
def event_filter
filters = cookies['event_filter'].split(',') if cookies['event_filter'].present?
@event_filter ||= EventFilter.new(filters)
end
+ def gitlab_ldap_access(&block)
+ Gitlab::LDAP::Access.open { |access| block.call(access) }
+ end
+
# JSON for infinite scroll via Pager object
def pager_json(partial, count)
html = render_to_string(
@@ -211,4 +230,8 @@ class ApplicationController < ActionController::Base
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me) }
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :name, :password, :password_confirmation) }
end
+
+ def hexdigest(string)
+ Digest::SHA1.hexdigest string
+ end
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index b95233c0e91..233b91680f6 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -22,6 +22,8 @@ class DashboardController < ApplicationController
@last_push = current_user.recent_push
+ @publicish_project_count = Project.publicish(current_user).count
+
respond_to do |format|
format.html
format.json { pager_json("events/_events", @events.count) }
@@ -53,14 +55,15 @@ class DashboardController < ApplicationController
end
def merge_requests
- @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params)
+ @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
@merge_requests = @merge_requests.page(params[:page]).per(20)
+ @merge_requests = @merge_requests.preload(:author, :target_project)
end
def issues
- @issues = FilteringService.new.execute(Issue, current_user, params)
+ @issues = IssuesFinder.new.execute(current_user, params)
@issues = @issues.page(params[:page]).per(20)
- @issues = @issues.includes(:author, :project)
+ @issues = @issues.preload(:author, :project)
respond_to do |format|
format.html
@@ -77,5 +80,6 @@ class DashboardController < ApplicationController
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 7b418ec98f5..a3019b3ac78 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -1,4 +1,5 @@
class GroupsController < ApplicationController
+ skip_before_filter :authenticate_user!, only: [:show, :issues, :members, :merge_requests]
respond_to :html
before_filter :group, except: [:new, :create]
@@ -36,7 +37,7 @@ class GroupsController < ApplicationController
@events = Event.in_projects(project_ids)
@events = event_filter.apply_filter(@events)
@events = @events.limit(20).offset(params[:offset] || 0)
- @last_push = current_user.recent_push
+ @last_push = current_user.recent_push if current_user
respond_to do |format|
format.html
@@ -46,14 +47,15 @@ class GroupsController < ApplicationController
end
def merge_requests
- @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params)
+ @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
@merge_requests = @merge_requests.page(params[:page]).per(20)
+ @merge_requests = @merge_requests.preload(:author, :target_project)
end
def issues
- @issues = FilteringService.new.execute(Issue, current_user, params)
+ @issues = IssuesFinder.new.execute(current_user, params)
@issues = @issues.page(params[:page]).per(20)
- @issues = @issues.includes(:author, :project)
+ @issues = @issues.preload(:author, :project)
respond_to do |format|
format.html
@@ -63,7 +65,14 @@ class GroupsController < ApplicationController
def members
@project = group.projects.find(params[:project_id]) if params[:project_id]
- @members = group.users_groups.order('group_access DESC')
+ @members = group.users_groups
+
+ if params[:search].present?
+ users = group.users.search(params[:search])
+ @members = @members.where(user_id: users)
+ end
+
+ @members = @members.order('group_access DESC').page(params[:page]).per(50)
@users_group = UsersGroup.new
end
@@ -91,17 +100,21 @@ class GroupsController < ApplicationController
end
def projects
- @projects ||= current_user.authorized_projects.where(namespace_id: group.id).sorted_by_activity
+ @projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity.non_archived
end
def project_ids
- projects.map(&:id)
+ projects.pluck(:id)
end
# Dont allow unauthorized access to group
def authorize_read_group!
unless @group and (projects.present? or can?(current_user, :read_group, @group))
- return render_404
+ if current_user.nil?
+ return authenticate_user!
+ else
+ return render_404
+ end
end
end
@@ -124,13 +137,21 @@ class GroupsController < ApplicationController
def determine_layout
if [:new, :create].include?(action_name.to_sym)
'navless'
- else
+ elsif current_user
'group'
+ else
+ 'public_group'
end
end
def default_filter
- params[:scope] = 'assigned-to-me' if params[:scope].blank?
+ 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
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
new file mode 100644
index 00000000000..988ede3007b
--- /dev/null
+++ b/app/controllers/passwords_controller.rb
@@ -0,0 +1,18 @@
+class PasswordsController < Devise::PasswordsController
+
+ def create
+ email = resource_params[:email]
+ resource_found = resource_class.find_by_email(email)
+ if resource_found && resource_found.ldap_user?
+ flash[:alert] = "Cannot reset password for LDAP user."
+ respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name)) and return
+ end
+
+ self.resource = resource_class.send_reset_password_instructions(resource_params)
+ if successfully_sent?(resource)
+ respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name))
+ else
+ respond_with(resource)
+ end
+ end
+end
diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb
new file mode 100644
index 00000000000..9996b67a8a4
--- /dev/null
+++ b/app/controllers/profiles/emails_controller.rb
@@ -0,0 +1,26 @@
+class Profiles::EmailsController < ApplicationController
+ layout "profile"
+
+ def index
+ @primary = current_user.email
+ @emails = current_user.emails
+ end
+
+ def create
+ @email = current_user.emails.new(params[:email])
+
+ flash[:alert] = @email.errors.full_messages.first unless @email.save
+
+ redirect_to profile_emails_url
+ end
+
+ def destroy
+ @email = current_user.emails.find(params[:id])
+ @email.destroy
+
+ respond_to do |format|
+ format.html { redirect_to profile_emails_url }
+ format.js { render nothing: true }
+ end
+ end
+end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index b4f14e649e2..6713cd7c8c7 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -41,7 +41,7 @@ class Profiles::KeysController < ApplicationController
begin
user = User.find_by_username(params[:username])
if user.present?
- render text: user.all_ssh_keys.join("\n")
+ render text: user.all_ssh_keys.join("\n"), content_type: "text/plain"
else
render_404 and return
end
diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb
index 6bd1a455f32..ff5206b6fa1 100644
--- a/app/controllers/projects/edit_tree_controller.rb
+++ b/app/controllers/projects/edit_tree_controller.rb
@@ -2,6 +2,8 @@ class Projects::EditTreeController < Projects::BaseTreeController
before_filter :require_branch_head
before_filter :blob
before_filter :authorize_push!
+ before_filter :from_merge_request
+ before_filter :after_edit_path
def show
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
@@ -12,7 +14,12 @@ class Projects::EditTreeController < Projects::BaseTreeController
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- redirect_to project_blob_path(@project, @id)
+
+ if from_merge_request
+ from_merge_request.reload_code
+ end
+
+ redirect_to after_edit_path
else
flash[:alert] = result[:error]
render :show
@@ -24,4 +31,19 @@ class Projects::EditTreeController < Projects::BaseTreeController
def blob
@blob ||= @repository.blob_at(@commit.id, @path)
end
+
+ def after_edit_path
+ @after_edit_path ||=
+ if from_merge_request
+ diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
+ "#file-path-#{hexdigest(@path)}"
+ else
+ project_blob_path(@project, @id)
+ end
+ end
+
+ def from_merge_request
+ # If blob edit was initiated from merge request page
+ @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
+ end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index ba5c52d510f..eef849d8209 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -28,7 +28,7 @@ class Projects::IssuesController < Projects::ApplicationController
@milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
sort_param = params[:sort] || 'newest'
@sort = sort_param.humanize unless sort_param.empty?
-
+ @assignees = User.where(id: @project.issues.pluck(:assignee_id))
respond_to do |format|
format.html
@@ -76,7 +76,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def update
- @issue.update_attributes(params[:issue].merge(author_id_of_changes: current_user.id))
+ @issue.update_attributes(params[:issue])
@issue.reset_events_cache
respond_to do |format|
@@ -121,7 +121,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issues_filtered
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
- @issues = FilteringService.new.execute(Issue, current_user, params.merge(project_id: @project.id))
+ @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
end
# Since iids are implemented only in 6.1
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 65f9e2b9d57..0166ca9ff00 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -13,7 +13,7 @@ class Projects::LabelsController < Projects::ApplicationController
def generate
Gitlab::IssuesLabels.generate(@project)
- redirect_to project_labels_path(@project)
+ redirect_to project_issues_path(@project)
end
protected
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index de31ee12b6a..e6edceb1fbc 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -21,13 +21,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
- @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params.merge(project_id: @project.id))
+ @merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id))
@merge_requests = @merge_requests.page(params[:page]).per(20)
@sort = params[:sort].humanize
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
@assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
@milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
+ @assignees = User.where(id: @project.merge_requests.pluck(:assignee_id))
end
def show
@@ -59,7 +60,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def new
@merge_request = MergeRequest.new(params[:merge_request])
@merge_request.source_project = @project unless @merge_request.source_project
- @merge_request.target_project = @project unless @merge_request.target_project
+ @merge_request.target_project ||= (@project.forked_from_project || @project)
+ @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names
+
+ @merge_request.target_branch ||= @merge_request.target_project.default_branch
+
@source_project = @merge_request.source_project
@merge_request
end
@@ -104,11 +109,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params[:merge_request].delete(:source_project_id)
params[:merge_request].delete(:target_project_id)
- if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id))
- @merge_request.reload_code
- @merge_request.mark_as_unchecked
+ if @merge_request.update_attributes(params[:merge_request])
@merge_request.reset_events_cache
- redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.'
+
+ respond_to do |format|
+ format.js
+ format.html do
+ redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.'
+ end
+ end
else
render "edit"
end
@@ -126,7 +135,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def automerge
return access_denied! unless allowed_to_merge?
- if @merge_request.opened? && @merge_request.can_be_merged?
+ if @merge_request.open? && @merge_request.can_be_merged?
@merge_request.should_remove_source_branch = params[:should_remove_source_branch]
@merge_request.automerge!(current_user, params[:merge_commit_message])
@status = true
@@ -157,7 +166,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def ci_status
- status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
+ status = @merge_request.source_project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
response = {status: status}
render json: response
@@ -221,7 +230,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request_diff = @merge_request.merge_request_diff
@allowed_to_merge = allowed_to_merge?
- @show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge
+ @show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge
end
def allowed_to_merge?
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index aea92a19f34..05237bbd2d2 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -38,7 +38,6 @@ class Projects::MilestonesController < Projects::ApplicationController
def create
@milestone = @project.milestones.new(params[:milestone])
- @milestone.author_id_of_changes = current_user.id
if @milestone.save
redirect_to project_milestone_path(@project, @milestone)
@@ -48,7 +47,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def update
- @milestone.update_attributes(params[:milestone].merge(author_id_of_changes: current_user.id))
+ @milestone.update_attributes(params[:milestone])
respond_to do |format|
format.js
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 9c9c2decc78..85d042a89b5 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -5,7 +5,7 @@ class Projects::NotesController < Projects::ApplicationController
before_filter :authorize_admin_note!, only: [:update, :destroy]
def index
- @notes = Notes::LoadService.new(project, current_user, params).execute
+ @notes = NotesFinder.new.execute(project, current_user, params)
notes_json = { notes: [] }
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 18ace028b0c..a6b7ae3f127 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -11,11 +11,7 @@ class Projects::RawController < Projects::ApplicationController
@blob = @repository.blob_at(@commit.id, @path)
if @blob
- type = if @blob.mime_type =~ /html|javascript/
- 'text/plain; charset=utf-8'
- else
- @blob.mime_type
- end
+ type = get_blob_type
headers['X-Content-Type-Options'] = 'nosniff'
@@ -29,5 +25,17 @@ class Projects::RawController < Projects::ApplicationController
not_found!
end
end
+
+ private
+
+ def get_blob_type
+ if @blob.mime_type =~ /html|javascript/
+ 'text/plain; charset=utf-8'
+ elsif @blob.name =~ /(?:msi|exe|rar|r0\d|7z|7zip|zip)$/
+ 'application/octet-stream'
+ else
+ @blob.mime_type
+ end
+ end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 6ec109b9145..ebb8a90c630 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -5,7 +5,7 @@ class ProjectsController < ApplicationController
# Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create]
- before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
+ before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import]
before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
layout 'navless', only: [:new, :create, :fork]
@@ -21,16 +21,9 @@ class ProjectsController < ApplicationController
def create
@project = ::Projects::CreateService.new(current_user, params[:project]).execute
+ flash[:notice] = 'Project was successfully created.' if @project.saved?
respond_to do |format|
- flash[:notice] = 'Project was successfully created.' if @project.saved?
- format.html do
- if @project.saved?
- redirect_to @project
- else
- render "new"
- end
- end
format.js
end
end
@@ -55,6 +48,11 @@ class ProjectsController < ApplicationController
end
def show
+ if @project.import_in_progress?
+ redirect_to import_project_path(@project)
+ return
+ end
+
return authenticate_user! unless @project.public? || current_user
limit = (params[:limit] || 20).to_i
@@ -67,9 +65,7 @@ class ProjectsController < ApplicationController
if @project.empty_repo?
render "projects/empty", layout: user_layout
else
- if current_user
- @last_push = current_user.recent_push(@project.id)
- end
+ @last_push = current_user.recent_push(@project.id) if current_user
render :show, layout: user_layout
end
end
@@ -77,6 +73,28 @@ class ProjectsController < ApplicationController
end
end
+ def import
+ if project.import_finished?
+ redirect_to @project
+ return
+ end
+ end
+
+ def retry_import
+ unless @project.import_failed?
+ redirect_to import_project_path(@project)
+ end
+
+ @project.import_url = params[:project][:import_url]
+
+ if @project.save
+ @project.reload
+ @project.import_retry
+ end
+
+ redirect_to import_project_path(@project)
+ end
+
def destroy
return access_denied! unless can?(current_user, :remove_project, project)
@@ -105,10 +123,20 @@ class ProjectsController < ApplicationController
end
def autocomplete_sources
+ note_type = params['type']
+ note_id = params['type_id']
+ participating = if note_type && note_id
+ participants_in(note_type, note_id)
+ else
+ []
+ end
+ team_members = sorted(@project.team.members)
+ participants = team_members + participating
@suggestions = {
- emojis: Emoji.names,
+ emojis: Emoji.names.map { |e| { name: e, path: view_context.image_url("emoji/#{e}.png") } },
issues: @project.issues.select([:iid, :title, :description]),
- members: @project.team.members.sort_by(&:username).map { |user| { username: user.username, name: user.name } }
+ mergerequests: @project.merge_requests.select([:iid, :title, :description]),
+ members: participants.uniq
}
respond_to do |format|
@@ -143,4 +171,25 @@ class ProjectsController < ApplicationController
def user_layout
current_user ? "projects" : "public_projects"
end
+
+ def participants_in(type, id)
+ users = case type
+ when "Issue"
+ issue = @project.issues.find_by_iid(id)
+ issue ? issue.participants : []
+ when "MergeRequest"
+ merge_request = @project.merge_requests.find_by_iid(id)
+ merge_request ? merge_request.participants : []
+ when "Commit"
+ author_ids = Note.for_commit_id(id).pluck(:author_id).uniq
+ User.where(id: author_ids)
+ else
+ []
+ end
+ sorted(users)
+ end
+
+ def sorted(users)
+ users.uniq.sort_by(&:username).map { |user| { username: user.username, name: user.name } }
+ end
end
diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb
index d7297161c22..d6238f79547 100644
--- a/app/controllers/public/projects_controller.rb
+++ b/app/controllers/public/projects_controller.rb
@@ -6,7 +6,7 @@ class Public::ProjectsController < ApplicationController
layout 'public'
def index
- @projects = Project.public_or_internal_only(current_user)
+ @projects = Project.publicish(current_user)
@projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(20)
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index c1648d6c387..8df84e9884a 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -7,6 +7,7 @@ class SearchController < ApplicationController
if @project
return access_denied! unless can?(current_user, :download_code, @project)
+
@search_results = Search::ProjectService.new(@project, current_user, params).execute
else
@search_results = Search::GlobalService.new(current_user, params).execute
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index e54a968326f..0dd941a48e2 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -19,6 +19,9 @@ class SnippetsController < ApplicationController
def user_index
@user = User.find_by(username: params[:username])
+
+ render_404 and return unless @user
+
@snippets = @user.snippets.fresh.non_expired
if @user == current_user
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index e86601a439e..9461174b950 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,15 +1,15 @@
class UsersController < ApplicationController
-
skip_before_filter :authenticate_user!, only: [:show]
layout :determine_layout
def show
@user = User.find_by_username!(params[:username])
- @projects = @user.authorized_projects.includes(:namespace).select {|project| can?(current_user, :read_project, project)}
+ @projects = @user.authorized_projects.accessible_to(current_user)
if !current_user && @projects.empty?
return authenticate_user!
end
- @events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20)
+ @groups = @user.groups.accessible_to(current_user)
+ @events = @user.recent_events.where(project_id: @projects.pluck(:id)).limit(20)
@title = @user.name
end
diff --git a/app/finders/README.md b/app/finders/README.md
new file mode 100644
index 00000000000..47823c51efb
--- /dev/null
+++ b/app/finders/README.md
@@ -0,0 +1,22 @@
+# Finders
+
+This type of classes responsible for collectiong items based on different conditions.
+To prevent lookup methods in models like this:
+
+```ruby
+class Project
+ def issues_for_user_filtered_by(user, filter)
+ # A lot of logic not related to project model itself
+ end
+end
+
+issues = project.issues_for_user_filtered_by(user, params)
+```
+
+Better use this:
+
+```ruby
+issues = IssuesFinder.new.execute(project, user, filter)
+```
+
+It will help keep models thiner
diff --git a/app/services/filtering_service.rb b/app/finders/base_finder.rb
index 52537f7ba4f..7fc5840561c 100644
--- a/app/services/filtering_service.rb
+++ b/app/finders/base_finder.rb
@@ -1,4 +1,4 @@
-# FilteringService class
+# BaseFinder
#
# Used to filter Issues and MergeRequests collections by set of params
#
@@ -16,11 +16,10 @@
# label_name: string
# sort: string
#
-class FilteringService
- attr_accessor :klass, :current_user, :params
+class BaseFinder
+ attr_accessor :current_user, :params
- def execute(klass, current_user, params)
- @klass = klass
+ def execute(current_user, params)
@current_user = current_user
@params = params
@@ -41,16 +40,16 @@ class FilteringService
def init_collection
table_name = klass.table_name
- return klass.of_projects(Project.public_only) unless current_user
-
if project
- if current_user.can?(:read_project, project)
+ if project.public? || (current_user && current_user.can?(:read_project, project))
project.send(table_name)
else
[]
end
+ elsif current_user && params[:authorized_only].presence
+ klass.of_projects(current_user.authorized_projects).references(:project)
else
- klass.of_projects(current_user.authorized_projects)
+ klass.of_projects(Project.accessible_to(current_user)).references(:project)
end
end
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
new file mode 100644
index 00000000000..8e0c606249e
--- /dev/null
+++ b/app/finders/issues_finder.rb
@@ -0,0 +1,22 @@
+# Finders::Issues class
+#
+# Used to filter Issues collections by set of params
+#
+# Arguments:
+# current_user - which user use
+# params:
+# scope: 'created-by-me' or 'assigned-to-me' or 'all'
+# state: 'open' or 'closed' or 'all'
+# group_id: integer
+# project_id: integer
+# milestone_id: integer
+# assignee_id: integer
+# search: string
+# label_name: string
+# sort: string
+#
+class IssuesFinder < BaseFinder
+ def klass
+ Issue
+ end
+end
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
new file mode 100644
index 00000000000..3727149c8fb
--- /dev/null
+++ b/app/finders/merge_requests_finder.rb
@@ -0,0 +1,22 @@
+# Finders::MergeRequest class
+#
+# Used to filter MergeRequests collections by set of params
+#
+# Arguments:
+# current_user - which user use
+# params:
+# scope: 'created-by-me' or 'assigned-to-me' or 'all'
+# state: 'open' or 'closed' or 'all'
+# group_id: integer
+# project_id: integer
+# milestone_id: integer
+# assignee_id: integer
+# search: string
+# label_name: string
+# sort: string
+#
+class MergeRequestsFinder < BaseFinder
+ def klass
+ MergeRequest
+ end
+end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
new file mode 100644
index 00000000000..384316e14b7
--- /dev/null
+++ b/app/finders/notes_finder.rb
@@ -0,0 +1,17 @@
+class NotesFinder
+ def execute(project, current_user, params)
+ target_type = params[:target_type]
+ target_id = params[:target_id]
+
+ case target_type
+ when "commit"
+ project.notes.for_commit_id(target_id).not_inline.fresh
+ when "issue"
+ project.issues.find(target_id).notes.inc_author.fresh
+ when "merge_request"
+ project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh
+ when "snippet"
+ project.snippets.find(target_id).notes.fresh
+ end
+ end
+end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
new file mode 100644
index 00000000000..bfaba758788
--- /dev/null
+++ b/app/finders/projects_finder.rb
@@ -0,0 +1,63 @@
+class ProjectsFinder
+ def execute(current_user, options)
+ group = options[:group]
+
+ if group
+ group_projects(current_user, group)
+ else
+ all_projects(current_user)
+ end
+ end
+
+ private
+
+ def group_projects(current_user, group)
+ if current_user
+ if group.users.include?(current_user)
+ # User is group member
+ #
+ # Return ALL group projects
+ group.projects
+ else
+ projects_members = UsersProject.where(
+ project_id: group.projects,
+ user_id: current_user
+ )
+
+ if projects_members.any?
+ # User is a project member
+ #
+ # Return only:
+ # public projects
+ # internal projects
+ # joined projects
+ #
+ group.projects.where(
+ "projects.id IN (?) OR projects.visibility_level IN (?)",
+ projects_members.pluck(:project_id),
+ Project.public_and_internal_levels
+ )
+ else
+ # User has no access to group or group projects
+ #
+ # Return only:
+ # public projects
+ # internal projects
+ #
+ group.projects.public_and_internal_only
+ end
+ end
+ else
+ # Not authenticated
+ #
+ # Return only:
+ # public projects
+ group.projects.public_only
+ end
+ end
+
+ def all_projects
+ # TODO: implement
+ raise 'Not implemented yet'
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 1550e8b7e05..faecde299c1 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -54,7 +54,7 @@ module ApplicationHelper
if group && group.avatar.present?
group.avatar.url
else
- '/assets/no_group_avatar.png'
+ image_path('no_group_avatar.png')
end
end
@@ -89,16 +89,15 @@ module ApplicationHelper
"Never"
end
- def grouped_options_refs(destination = :tree)
+ def grouped_options_refs
repository = @project.repository
options = [
["Branches", repository.branch_names],
- ["Tags", repository.tag_names]
+ ["Tags", VersionSorter.rsort(repository.tag_names)]
]
- # If reference is commit id -
- # we should add it to branch/tag selectbox
+ # If reference is commit id - we should add it to branch/tag selectbox
if(@ref && !options.flatten.include?(@ref) &&
@ref =~ /^[0-9a-zA-Z]{6,52}$/)
options << ["Commit", [@ref]]
@@ -147,8 +146,7 @@ module ApplicationHelper
def authbutton(provider, size = 64)
file_name = "#{provider.to_s.split('_').first}_#{size}.png"
- image_tag("authbuttons/#{file_name}",
- alt: "Sign in with #{provider.to_s.titleize}")
+ image_tag(image_path("authbuttons/#{file_name}"), alt: "Sign in with #{provider.to_s.titleize}")
end
def simple_sanitize(str)
@@ -162,15 +160,6 @@ module ApplicationHelper
alias_method :url_to_image, :image_url
- def users_select_tag(id, opts = {})
- css_class = "ajax-users-select "
- css_class << "multiselect " if opts[:multiple]
- css_class << (opts[:class] || '')
- value = opts[:selected] || ''
-
- hidden_field_tag(id, value, class: css_class)
- end
-
def body_data_page
path = controller.controller_path.split('/')
namespace = path.first if path.second
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 663369e4584..c6e4f574b67 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -105,8 +105,80 @@ module CommitsHelper
branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe
end
- def get_old_file(project, commit, diff)
- project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id
+ def parallel_diff_lines(project, commit, diff, file)
+ old_file = project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id
+ deleted_lines = {}
+ added_lines = {}
+ each_diff_line(diff, 0) do |line, type, line_code, line_new, line_old|
+ if type == "old"
+ deleted_lines[line_old] = { line_code: line_code, type: type, line: line }
+ elsif type == "new"
+ added_lines[line_new] = { line_code: line_code, type: type, line: line }
+ end
+ end
+ max_length = old_file ? old_file.sloc + added_lines.length : file.sloc
+
+ offset1 = 0
+ offset2 = 0
+ old_lines = []
+ new_lines = []
+
+ max_length.times do |line_index|
+ line_index1 = line_index - offset1
+ line_index2 = line_index - offset2
+ deleted_line = deleted_lines[line_index1 + 1]
+ added_line = added_lines[line_index2 + 1]
+ old_line = old_file.lines[line_index1] if old_file
+ new_line = file.lines[line_index2]
+
+ if deleted_line && added_line
+ elsif deleted_line
+ new_line = nil
+ offset2 += 1
+ elsif added_line
+ old_line = nil
+ offset1 += 1
+ end
+
+ old_lines[line_index] = DiffLine.new
+ new_lines[line_index] = DiffLine.new
+
+ # old
+ if line_index == 0 && diff.new_file
+ old_lines[line_index].type = :file_created
+ old_lines[line_index].content = 'File was created'
+ elsif deleted_line
+ old_lines[line_index].type = :deleted
+ old_lines[line_index].content = old_line
+ old_lines[line_index].num = line_index1 + 1
+ old_lines[line_index].code = deleted_line[:line_code]
+ elsif old_line
+ old_lines[line_index].type = :no_change
+ old_lines[line_index].content = old_line
+ old_lines[line_index].num = line_index1 + 1
+ else
+ old_lines[line_index].type = :added
+ end
+
+ # new
+ if line_index == 0 && diff.deleted_file
+ new_lines[line_index].type = :file_deleted
+ new_lines[line_index].content = "File was deleted"
+ elsif added_line
+ new_lines[line_index].type = :added
+ new_lines[line_index].num = line_index2 + 1
+ new_lines[line_index].content = new_line
+ new_lines[line_index].code = added_line[:line_code]
+ elsif new_line
+ new_lines[line_index].type = :no_change
+ new_lines[line_index].num = line_index2 + 1
+ new_lines[line_index].content = new_line
+ else
+ new_lines[line_index].type = :deleted
+ end
+ end
+
+ return old_lines, new_lines
end
protected
@@ -122,17 +194,18 @@ module CommitsHelper
def commit_person_link(commit, options = {})
source_name = commit.send "#{options[:source]}_name".to_sym
source_email = commit.send "#{options[:source]}_email".to_sym
+
+ user = User.find_for_commit(source_email, source_name)
+ person_name = user.nil? ? source_name : user.name
+ person_email = user.nil? ? source_email : user.email
+
text = if options[:avatar]
- avatar = image_tag(avatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
- %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>}
+ avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
+ %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{person_name}</span>}
else
- source_name
+ person_name
end
- # Prefer email match over name match
- user = User.where(email: source_email).first
- user ||= User.where(name: source_name).first
-
options = {
class: "commit-#{options[:source]}-link has_tooltip",
data: { :'original-title' => sanitize(source_email) }
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index b25662e3ba2..69425bc171d 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -35,7 +35,6 @@ module GitlabMarkdownHelper
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
filter_html: true,
with_toc_data: true,
- hard_wrap: true,
safe_links_only: true
}.merge(options))
@markdown = Redcarpet::Markdown.new(gitlab_renderer,
@@ -45,7 +44,7 @@ module GitlabMarkdownHelper
fenced_code_blocks: true,
autolink: true,
strikethrough: true,
- lax_html_blocks: true,
+ lax_spacing: true,
space_after_headers: true,
superscript: true)
end
@@ -64,8 +63,7 @@ module GitlabMarkdownHelper
# project_path_with_namespace - namespace/projectname, eg. gitlabhq/gitlabhq
# ref - name of the branch or reference, eg. stable
# requested_path - path of request, eg. doc/api/README.md, used in special case when path is pointing to the .md file were the original request is coming from
- # wiki - whether the markdown is from wiki or not
- def create_relative_links(text, project, ref, requested_path, wiki = false)
+ def create_relative_links(text, project, ref, requested_path)
@path_to_satellite = project.satellite.path
project_path_with_namespace = project.path_with_namespace
paths = extract_paths(text)
@@ -124,21 +122,23 @@ module GitlabMarkdownHelper
end
def rebuild_path(path_with_namespace, path, requested_path, ref)
+ path.gsub!(/(#.*)/, "")
+ id = $1 || ""
file_path = relative_file_path(path, requested_path)
[
path_with_namespace,
path_with_ref(file_path, ref),
file_path
- ].compact.join("/")
+ ].compact.join("/").gsub(/\/*$/, '') + id
end
# Checks if the path exists in the repo
- # eg. checks if doc/README.md exists, if it doesn't then it is a wiki link
+ # eg. checks if doc/README.md exists, if not then link to blob
def path_with_ref(path, ref)
if file_exists?(path)
"#{local_path(path)}/#{correct_ref(ref)}"
else
- "wikis"
+ "blob/#{correct_ref(ref)}"
end
end
@@ -154,6 +154,7 @@ module GitlabMarkdownHelper
# If we are at doc/api and the README.md shown in below the tree view
# this takes the rquest path(doc/api) and adds users.md so the path looks like doc/api/users.md
def build_nested_path(path, request_path)
+ return request_path if path == ""
return path unless request_path
if local_path(request_path) == "tree"
base = request_path.split("/").push(path)
@@ -166,7 +167,7 @@ module GitlabMarkdownHelper
end
def file_exists?(path)
- return false if path.nil? || path.empty?
+ return false if path.nil?
return @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any?
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 5865396b698..cfc9a572cac 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -6,6 +6,14 @@ 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)
+ else
+ false
+ end
+ end
def group_head_title
title = @group.name
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index cdba6ce84dc..95f0eff58b1 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -70,11 +70,11 @@ module IssuesHelper
end
def bulk_update_milestone_options
- options_for_select(["None (backlog)", nil]) + options_from_collection_for_select(project_active_milestones, "id", "title", params[:milestone_id])
+ options_for_select(["None (backlog)"]) + options_from_collection_for_select(project_active_milestones, "id", "title", params[:milestone_id])
end
def bulk_update_assignee_options
- options_for_select(["None (unassigned)", nil]) + options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id])
+ options_for_select(["None (unassigned)"]) + options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id])
end
def assignee_options object
@@ -84,4 +84,16 @@ module IssuesHelper
def milestone_options object
options_from_collection_for_select(@project.milestones.active, 'id', 'title', object.milestone_id)
end
+
+ def issue_box_class(item)
+ if item.respond_to?(:expired?) && item.expired?
+ 'issue-box-expired'
+ elsif item.respond_to?(:merged?) && item.merged?
+ 'issue-box-merged'
+ elsif item.closed?
+ 'issue-box-closed'
+ else
+ 'issue-box-open'
+ end
+ end
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 5e3f82fe9ce..ba25a87f392 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -1,8 +1,9 @@
module MergeRequestsHelper
def new_mr_path_from_push_event(event)
+ target_project = event.project.forked_from_project || event.project
new_project_merge_request_path(
event.project,
- new_mr_from_push_event(event, event.project)
+ new_mr_from_push_event(event, target_project)
)
end
@@ -19,7 +20,7 @@ module MergeRequestsHelper
target_project_id: target_project.id,
source_branch: event.branch_name,
target_branch: target_project.repository.root_ref,
- title: event.branch_name.titleize
+ title: event.branch_name.humanize
}
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index ae3402b2617..b2399bb6db1 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -1,11 +1,11 @@
module NotificationsHelper
def notification_icon(notification)
if notification.disabled?
- content_tag :i, nil, class: 'icon-circle cred'
+ content_tag :i, nil, class: 'icon-volume-off cred'
elsif notification.participating?
- content_tag :i, nil, class: 'icon-circle cblue'
+ content_tag :i, nil, class: 'icon-volume-down cblue'
elsif notification.watch?
- content_tag :i, nil, class: 'icon-circle cgreen'
+ content_tag :i, nil, class: 'icon-volume-up cgreen'
else
content_tag :i, nil, class: 'icon-circle-blank cblue'
end
diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb
index dd9e03d95a8..297ae83d895 100644
--- a/app/helpers/profile_helper.rb
+++ b/app/helpers/profile_helper.rb
@@ -10,7 +10,7 @@ module ProfileHelper
end
def show_profile_social_tab?
- Gitlab.config.omniauth.enabled && !current_user.ldap_user?
+ enabled_social_providers.any? && !current_user.ldap_user?
end
def show_profile_remove_tab?
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index a6a507360bd..e943045e414 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -64,6 +64,31 @@ 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],
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 470a495f036..d7f3da7e537 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -4,8 +4,7 @@ module SearchHelper
resources_results = [
groups_autocomplete(term),
- projects_autocomplete(term),
- public_projects_autocomplete(term),
+ projects_autocomplete(term)
].flatten
generic_results = project_autocomplete + default_autocomplete + help_autocomplete
@@ -82,17 +81,7 @@ module SearchHelper
# Autocomplete results for the current user's projects
def projects_autocomplete(term, limit = 5)
- current_user.authorized_projects.search_by_title(term).non_archived.limit(limit).map do |p|
- {
- label: "project: #{search_result_sanitize(p.name_with_namespace)}",
- url: project_path(p)
- }
- end
- end
-
- # Autocomplete results for the current user's projects
- def public_projects_autocomplete(term, limit = 5)
- Project.public_or_internal_only(current_user).search_by_title(term).non_archived.limit(limit).map do |p|
+ Project.accessible_to(current_user).search_by_title(term).non_archived.limit(limit).map do |p|
{
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
url: project_path(p)
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
new file mode 100644
index 00000000000..a1fe4488ae9
--- /dev/null
+++ b/app/helpers/selects_helper.rb
@@ -0,0 +1,20 @@
+module SelectsHelper
+ def users_select_tag(id, opts = {})
+ css_class = "ajax-users-select "
+ css_class << "multiselect " if opts[:multiple]
+ css_class << (opts[:class] || '')
+ value = opts[:selected] || ''
+
+ hidden_field_tag(id, value, class: css_class)
+ end
+
+ def project_users_select_tag(id, opts = {})
+ css_class = "ajax-project-users-select "
+ css_class << "multiselect " if opts[:multiple]
+ css_class << (opts[:class] || '')
+ value = opts[:selected] || ''
+ placeholder = opts[:placeholder] || 'Select user'
+
+ hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder)
+ end
+end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 2dbc1cffb16..50501dffefb 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -40,7 +40,7 @@ module TreeHelper
# Returns boolean
def markup?(filename)
filename.downcase.end_with?(*%w(.textile .rdoc .org .creole
- .mediawiki .rst .asciidoc .pod))
+ .mediawiki .rst .adoc .asciidoc .pod))
end
def gitlab_markdown?(filename)
diff --git a/app/mailers/emails/groups.rb b/app/mailers/emails/groups.rb
index 1c8ae122c46..1654fc55bca 100644
--- a/app/mailers/emails/groups.rb
+++ b/app/mailers/emails/groups.rb
@@ -3,7 +3,7 @@ module Emails
def group_access_granted_email(user_group_id)
@membership = UsersGroup.find(user_group_id)
@group = @membership.group
-
+ @target_url = group_url(@group)
mail(to: @membership.user.email,
subject: subject("Access to group was granted"))
end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index b2b4b83d6c3..d684e354452 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -3,22 +3,30 @@ module Emails
def new_issue_email(recipient_id, issue_id)
@issue = Issue.find(issue_id)
@project = @issue.project
- mail(to: recipient(recipient_id), subject: subject("New issue ##{@issue.iid}", @issue.title))
+ @target_url = project_issue_url(@project, @issue)
+ mail(from: sender(@issue.author_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@issue.title} (##{@issue.iid})"))
end
- def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
+ def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id)
@issue = Issue.find(issue_id)
@previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
@project = @issue.project
- mail(to: recipient(recipient_id), subject: subject("Changed issue ##{@issue.iid}", @issue.title))
+ @target_url = project_issue_url(@project, @issue)
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@issue.title} (##{@issue.iid})"))
end
def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
@issue = Issue.find issue_id
@project = @issue.project
@updated_by = User.find updated_by_user_id
- mail(to: recipient(recipient_id),
- subject: subject("Closed issue ##{@issue.iid}", @issue.title))
+ @target_url = project_issue_url(@project, @issue)
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@issue.title} (##{@issue.iid})"))
end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
@@ -26,8 +34,10 @@ module Emails
@issue_status = status
@project = @issue.project
@updated_by = User.find updated_by_user_id
- mail(to: recipient(recipient_id),
- subject: subject("Changed issue ##{@issue.iid}", @issue.title))
+ @target_url = project_issue_url(@project, @issue)
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@issue.title} (##{@issue.iid})"))
end
end
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index e60887d525a..a97d55f1b50 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -3,27 +3,39 @@ module Emails
def new_merge_request_email(recipient_id, merge_request_id)
@merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
- mail(to: recipient(recipient_id), subject: subject("New merge request ##{@merge_request.iid}", @merge_request.title))
+ @target_url = project_merge_request_url(@project, @merge_request)
+ mail(from: sender(@merge_request.author_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
end
- def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
+ def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_id)
@previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
@project = @merge_request.project
- mail(to: recipient(recipient_id), subject: subject("Changed merge request ##{@merge_request.iid}", @merge_request.title))
+ @target_url = project_merge_request_url(@project, @merge_request)
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
end
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_id)
@updated_by = User.find updated_by_user_id
@project = @merge_request.project
- mail(to: recipient(recipient_id), subject: subject("Closed merge request ##{@merge_request.iid}", @merge_request.title))
+ @target_url = project_merge_request_url(@project, @merge_request)
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
end
- def merged_merge_request_email(recipient_id, merge_request_id)
+ def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
- mail(to: recipient(recipient_id), subject: subject("Accepted merge request ##{@merge_request.iid}", @merge_request.title))
+ @target_url = project_merge_request_url(@project, @merge_request)
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
end
end
@@ -57,7 +69,7 @@ module Emails
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "GitLab Merge Request | Lorem ipsum | Dolor sit amet"
def subject(*extra)
- subject = "GitLab Merge Request |"
+ subject = "Merge Request | "
if @merge_request.for_fork?
subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}"
else
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index e967cf6dc73..ccbdadf010f 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -4,27 +4,39 @@ module Emails
@note = Note.find(note_id)
@commit = @note.noteable
@project = @note.project
- mail(to: recipient(recipient_id), subject: subject("Note for commit #{@commit.short_id}", @commit.title))
+ @target_url = project_commit_url(@project, @commit, anchor: "note_#{@note.id}")
+ mail(from: sender(@note.author_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@commit.title} (#{@commit.short_id})"))
end
def note_issue_email(recipient_id, note_id)
@note = Note.find(note_id)
@issue = @note.noteable
@project = @note.project
- mail(to: recipient(recipient_id), subject: subject("Note for issue ##{@issue.iid}"))
+ @target_url = project_issue_url(@project, @issue, anchor: "note_#{@note.id}")
+ mail(from: sender(@note.author_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@issue.title} (##{@issue.iid})"))
end
def note_merge_request_email(recipient_id, note_id)
@note = Note.find(note_id)
@merge_request = @note.noteable
@project = @note.project
- mail(to: recipient(recipient_id), subject: subject("Note for merge request ##{@merge_request.iid}"))
+ @target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{@note.id}")
+ mail(from: sender(@note.author_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
end
def note_wall_email(recipient_id, note_id)
@note = Note.find(note_id)
@project = @note.project
- mail(to: recipient(recipient_id), subject: subject("Note on wall"))
+ @target_url = project_wall_url(@note.project, anchor: "note_#{@note.id}")
+ mail(from: sender(@note.author_id),
+ to: recipient(recipient_id),
+ subject: subject("Note on wall"))
end
end
end
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index bcd44f9476c..f02d95fd557 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -3,12 +3,20 @@ module Emails
def new_user_email(user_id, password)
@user = User.find(user_id)
@password = password
+ @target_url = user_url(@user)
mail(to: @user.email, subject: subject("Account was created for you"))
end
+ def new_email_email(email_id)
+ @email = Email.find(email_id)
+ @user = @email.user
+ mail(to: @user.email, subject: subject("Email was added to your account"))
+ end
+
def new_ssh_key_email(key_id)
@key = Key.find(key_id)
@user = @key.user
+ @target_url = user_url(@user)
mail(to: @user.email, subject: subject("SSH key was added to your account"))
end
end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 428d74d83c6..46aa34d13da 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -3,6 +3,7 @@ module Emails
def project_access_granted_email(user_project_id)
@users_project = UsersProject.find user_project_id
@project = @users_project.project
+ @target_url = project_url(@project)
mail(to: @users_project.user.email,
subject: subject("Access to project was granted"))
end
@@ -10,6 +11,7 @@ module Emails
def project_was_moved_email(project_id, user_id)
@user = User.find user_id
@project = Project.find project_id
+ @target_url = project_url(@project)
mail(to: @user.email,
subject: subject("Project was moved"))
end
@@ -21,8 +23,15 @@ module Emails
@commits = Commit.decorate(compare.commits)
@diffs = compare.diffs
@branch = branch
+ if @commits.length > 1
+ @target_url = project_compare_url(@project, from: @commits.first, to: @commits.last)
+ else
+ @target_url = project_commit_url(@project, @compare.commit)
+ end
- mail(to: recipient, subject: subject("New push to repository"))
+ mail(from: sender(author_id),
+ to: recipient,
+ subject: subject("New push to repository"))
end
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 6c1a3328960..554f53cf148 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -15,16 +15,33 @@ class Notify < ActionMailer::Base
default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
- default from: Gitlab.config.gitlab.email_from
+ default from: Proc.new { default_sender_address.format }
default reply_to: "noreply@#{Gitlab.config.gitlab.host}"
- # Just send email with 3 seconds delay
+ # Just send email with 2 seconds delay
def self.delay
delay_for(2.seconds)
end
private
+ # The default email address to send emails from
+ def default_sender_address
+ address = Mail::Address.new(Gitlab.config.gitlab.email_from)
+ address.display_name = "GitLab"
+ address
+ end
+
+ # Return an email address that displays the name of the sender.
+ # Only the displayed name changes; the actual email address is always the same.
+ def sender(sender_id)
+ if sender = User.find(sender_id)
+ address = default_sender_address
+ address.display_name = sender.name
+ address.format
+ end
+ end
+
# Look up a User by their ID and return their email address
#
# recipient_id - User ID
@@ -43,21 +60,21 @@ class Notify < ActionMailer::Base
# Examples
#
# >> subject('Lorem ipsum')
- # => "GitLab | Lorem ipsum"
+ # => "Lorem ipsum"
#
# # Automatically inserts Project name when @project is set
# >> @project = Project.last
# => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# >> subject('Lorem ipsum')
- # => "GitLab | Ruby on Rails | Lorem ipsum "
+ # => "Ruby on Rails | Lorem ipsum "
#
# # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet')
- # => "GitLab | Lorem ipsum | Dolor sit amet"
+ # => "Lorem ipsum | Dolor sit amet"
def subject(*extra)
- subject = "GitLab"
- subject << (@project ? " | #{@project.name_with_namespace}" : "")
- subject << " | " + extra.join(' | ') if extra.present?
+ subject = ""
+ subject << "#{@project.name} | " if @project
+ subject << extra.join(' | ') if extra.present?
subject
end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index ba0ce527f64..1afe8a4638f 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -43,7 +43,19 @@ class Ability
:download_code
]
else
- []
+ group = if subject.kind_of?(Group)
+ subject
+ elsif subject.respond_to?(:group)
+ subject.group
+ else
+ nil
+ end
+
+ if group && group.has_projects_accessible_to?(nil)
+ [:read_group]
+ else
+ []
+ end
end
end
@@ -172,7 +184,7 @@ class Ability
def group_abilities user, group
rules = []
- if group.users.include?(user) || user.admin?
+ if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any?
rules << :read_group
end
@@ -228,6 +240,7 @@ class Ability
can_manage = group_abilities(user, group).include?(:manage_group)
if can_manage && (user != target_user)
rules << :modify
+ rules << :destroy
end
if !group.last_owner?(user) && (can_manage || (user == target_user))
rules << :destroy
diff --git a/app/models/commit.rb b/app/models/commit.rb
index bcc1bcbd96a..c313aeb7572 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -99,14 +99,16 @@ class Commit
#
# cut off, ellipses (`&hellp;`) are prepended to the commit message.
def description
- description = safe_message
+ title_end = safe_message.index(/\n/)
+ @description ||= if (!title_end && safe_message.length > 100) || (title_end && title_end > 100)
+ "&hellip;".html_safe << safe_message[80..-1]
+ else
+ safe_message.split(/\n/, 2)[1].try(:chomp)
+ end
+ end
- title_end = description.index(/\n/)
- if (!title_end && description.length > 100) || (title_end && title_end > 100)
- "&hellip;".html_safe << description[80..-1]
- else
- description.split(/\n/, 2)[1].try(:chomp)
- end
+ def description?
+ description.present?
end
# Regular expression that identifies commit message clauses that trigger issue closing.
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index bf2c2157d38..de167f8803a 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -23,7 +23,8 @@ module Issuable
scope :assigned, -> { where("assignee_id IS NOT NULL") }
scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) }
-
+ scope :opened, -> { with_state(:opened, :reopened) }
+ scope :closed, -> { with_state(:closed) }
delegate :name,
:email,
@@ -36,8 +37,6 @@ module Issuable
allow_nil: true,
prefix: true
- attr_accessor :author_id_of_changes
-
attr_mentionable :title, :description
end
diff --git a/app/models/diff_line.rb b/app/models/diff_line.rb
new file mode 100644
index 00000000000..ad37945874a
--- /dev/null
+++ b/app/models/diff_line.rb
@@ -0,0 +1,3 @@
+class DiffLine
+ attr_accessor :type, :content, :num, :code
+end
diff --git a/app/models/email.rb b/app/models/email.rb
new file mode 100644
index 00000000000..22e71e4f107
--- /dev/null
+++ b/app/models/email.rb
@@ -0,0 +1,33 @@
+# == Schema Information
+#
+# Table name: emails
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# email :string not null
+# created_at :datetime not null
+class Email < ActiveRecord::Base
+ attr_accessible :email, :user_id
+
+ #
+ # Relations
+ #
+ belongs_to :user
+
+ #
+ # Validations
+ #
+ validates :user_id, presence: true
+ validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
+ validate :unique_email, if: ->(email) { email.email_changed? }
+
+ before_validation :cleanup_email
+
+ def cleanup_email
+ self.email = self.email.downcase.strip
+ end
+
+ def unique_email
+ self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email)
+ end
+end \ No newline at end of file
diff --git a/app/models/event.rb b/app/models/event.rb
index ddb863c1be2..5c156856d79 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -47,20 +47,14 @@ class Event < ActiveRecord::Base
scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent }
class << self
- def determine_action(record)
- if [Issue, MergeRequest].include? record.class
- Event::CREATED
- elsif record.kind_of? Note
- Event::COMMENTED
- end
- end
-
def create_ref_event(project, user, ref, action = 'add', prefix = 'refs/heads')
+ commit = project.repository.commit(ref.target)
+
if action.to_s == 'add'
before = '00000000'
- after = ref.commit.id
+ after = commit.id
else
- before = ref.commit.id
+ before = commit.id
after = '00000000'
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 8de0c78c158..0d4d5f4e836 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -25,6 +25,12 @@ class Group < Namespace
validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
mount_uploader :avatar, AttachmentUploader
+
+ def self.accessible_to(user)
+ accessible_ids = Project.accessible_to(user).pluck(:namespace_id)
+ accessible_ids += user.groups.pluck(:id) if user
+ where(id: accessible_ids)
+ end
def human_name
name
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 6580c5004af..21040327741 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -28,12 +28,9 @@ class Issue < ActiveRecord::Base
scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
- scope :opened, -> { with_state(:opened, :reopened) }
- scope :closed, -> { with_state(:closed) }
attr_accessible :title, :assignee_id, :position, :description,
- :milestone_id, :label_list, :author_id_of_changes,
- :state_event
+ :milestone_id, :label_list, :state_event
acts_as_taggable_on :labels
@@ -50,9 +47,7 @@ class Issue < ActiveRecord::Base
end
state :opened
-
state :reopened
-
state :closed
end
diff --git a/app/models/key.rb b/app/models/key.rb
index 79f7bbd2590..29a76f53f3d 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -53,7 +53,7 @@ class Key < ActiveRecord::Base
Tempfile.open('gitlab_key_file') do |file|
file.puts key
file.rewind
- cmd_output, cmd_status = popen("ssh-keygen -lf #{file.path}", '/tmp')
+ cmd_output, cmd_status = popen(%W(ssh-keygen -lf #{file.path}), '/tmp')
end
if cmd_status.zero?
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index ca2644ec735..5c2b0656d40 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -32,11 +32,13 @@ class MergeRequest < ActiveRecord::Base
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
has_one :merge_request_diff, dependent: :destroy
+
after_create :create_merge_request_diff
+ after_update :update_merge_request_diff
delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil
- attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event, :description
+ attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :state_event, :description
attr_accessor :should_remove_source_branch
@@ -98,8 +100,6 @@ class MergeRequest < ActiveRecord::Base
scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
- scope :opened, -> { with_state(:opened) }
- scope :closed, -> { with_state(:closed) }
scope :merged, -> { with_state(:merged) }
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
@@ -125,8 +125,15 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def update_merge_request_diff
+ if source_branch_changed? || target_branch_changed?
+ reload_code
+ mark_as_unchecked
+ end
+ end
+
def reload_code
- if merge_request_diff && opened?
+ if merge_request_diff && open?
merge_request_diff.reload_content
end
end
@@ -151,6 +158,10 @@ class MergeRequest < ActiveRecord::Base
MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message)
end
+ def open?
+ opened? || reopened?
+ end
+
def mr_and_commit_notes
# Fetch comments only from last 100 commits
commits_for_notes_limit = 100
@@ -219,6 +230,14 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def source_project_namespace
+ if source_project && source_project.namespace
+ source_project.namespace.path
+ else
+ "(removed)"
+ end
+ end
+
def source_branch_exists?
return false unless self.source_project
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index a226fef7346..99afffc1db0 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -69,12 +69,6 @@ class MergeRequestDiff < ActiveRecord::Base
end
end
- # When Git::Diff is not able to get diff
- # because of git timeout it return this value
- def broken_diffs
- [Gitlab::Git::Diff::BROKEN_DIFF]
- end
-
# Collect array of Git::Commit objects
# between target and source branches
def unmerged_commits
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 1a73fa71e48..e16529a634c 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -16,8 +16,7 @@
class Milestone < ActiveRecord::Base
include InternalId
- attr_accessible :title, :description, :due_date, :state_event, :author_id_of_changes
- attr_accessor :author_id_of_changes
+ attr_accessible :title, :description, :due_date, :state_event
belongs_to :project
has_many :issues
@@ -89,6 +88,6 @@ class Milestone < ActiveRecord::Base
end
def author_id
- author_id_of_changes
+ nil
end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index d5b98f588e8..468c93bd426 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -27,7 +27,7 @@ class Namespace < ActiveRecord::Base
format: { with: Gitlab::Regex.name_regex,
message: "only letters, digits, spaces & '_' '-' '.' allowed." }
validates :description, length: { within: 0..255 }
- validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
+ validates :path, uniqueness: { case_sensitive: false }, presence: true, length: { within: 1..255 },
exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
@@ -47,6 +47,14 @@ class Namespace < ActiveRecord::Base
def self.global_id
'GLN'
end
+
+ def projects_accessible_to(user)
+ projects.accessible_to(user)
+ end
+
+ def has_projects_accessible_to?(user)
+ projects_accessible_to(user).present?
+ end
def to_param
path
diff --git a/app/models/note.rb b/app/models/note.rb
index 48c03c9d587..906de4855ab 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -199,7 +199,8 @@ class Note < ActiveRecord::Base
def downvote?
votable? && (note.start_with?('-1') ||
note.start_with?(':-1:') ||
- note.start_with?(':thumbsdown:')
+ note.start_with?(':thumbsdown:') ||
+ note.start_with?(':thumbs_down_sign:')
)
end
@@ -249,7 +250,8 @@ class Note < ActiveRecord::Base
def upvote?
votable? && (note.start_with?('+1') ||
note.start_with?(':+1:') ||
- note.start_with?(':thumbsup:')
+ note.start_with?(':thumbsup:') ||
+ note.start_with?(':thumbs_up_sign:')
)
end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index ff6a18d6a51..b0f8ed6a4ec 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -9,12 +9,23 @@ class Notification
attr_accessor :target
- def self.notification_levels
- [N_DISABLED, N_PARTICIPATING, N_WATCH]
- end
-
- def self.project_notification_levels
- [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL]
+ class << self
+ def notification_levels
+ [N_DISABLED, N_PARTICIPATING, N_WATCH]
+ end
+
+ def options_with_labels
+ {
+ disabled: N_DISABLED,
+ participating: N_PARTICIPATING,
+ watch: N_WATCH,
+ global: N_GLOBAL
+ }
+ end
+
+ def project_notification_levels
+ [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL]
+ end
end
def initialize(target)
@@ -36,4 +47,8 @@ class Notification
def global?
target.notification_level == N_GLOBAL
end
+
+ def level
+ target.notification_level
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index d9da2c377c8..769ab217625 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -28,6 +28,8 @@ class Project < ActiveRecord::Base
include Gitlab::VisibilityLevel
extend Enumerize
+ default_value_for :archived, false
+
ActsAsTaggableOn.strict_case_match = true
attr_accessible :name, :path, :description, :issues_tracker, :label_list,
@@ -53,15 +55,14 @@ class Project < ActiveRecord::Base
has_one :hipchat_service, dependent: :destroy
has_one :flowdock_service, dependent: :destroy
has_one :assembla_service, dependent: :destroy
+ has_one :gemnasium_service, dependent: :destroy
+ has_one :slack_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
-
# Merge Requests for target project should be removed with it
has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
-
# Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
-
has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy
has_many :services, dependent: :destroy
has_many :events, dependent: :destroy
@@ -70,10 +71,8 @@ class Project < ActiveRecord::Base
has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :protected_branches, dependent: :destroy
-
has_many :users_projects, dependent: :destroy
has_many :users, through: :users_projects
-
has_many :deploy_keys_projects, dependent: :destroy
has_many :deploy_keys, through: :deploy_keys_projects
@@ -93,15 +92,12 @@ class Project < ActiveRecord::Base
validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] }
validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
-
validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
-
validates :import_url,
format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" },
if: :import?
-
validate :check_limit, on: :create
# Scopes
@@ -114,18 +110,57 @@ class Project < ActiveRecord::Base
scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
- scope :public_only, -> { where(visibility_level: PUBLIC) }
- scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) }
-
+ scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
+ scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) }
enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
+ state_machine :import_status, initial: :none do
+ event :import_start do
+ transition :none => :started
+ end
+
+ event :import_finish do
+ transition :started => :finished
+ end
+
+ event :import_fail do
+ transition :started => :failed
+ end
+
+ event :import_retry do
+ transition :failed => :started
+ end
+
+ state :started
+ state :finished
+ state :failed
+
+ after_transition any => :started, :do => :add_import_job
+ end
+
class << self
+ def public_and_internal_levels
+ [Project::PUBLIC, Project::INTERNAL]
+ end
+
def abandoned
where('projects.last_activity_at < ?', 6.months.ago)
end
+ def publicish(user)
+ visibility_levels = [Project::PUBLIC]
+ visibility_levels += [Project::INTERNAL] if user
+ where(visibility_level: visibility_levels)
+ end
+
+ def accessible_to(user)
+ accessible_ids = publicish(user).pluck(:id)
+ accessible_ids += user.authorized_projects.pluck(:id) if user
+ where(id: accessible_ids)
+ end
+
def with_push
includes(:events).where('events.action = ?', Event::PUSHED)
end
@@ -143,15 +178,13 @@ class Project < ActiveRecord::Base
end
def find_with_namespace(id)
- if id.include?("/")
- id = id.split("/")
- namespace = Namespace.find_by(path: id.first)
- return nil unless namespace
-
- where(namespace_id: namespace.id).find_by(path: id.second)
- else
- where(path: id, namespace_id: nil).last
- end
+ return nil unless id.include?("/")
+
+ id = id.split("/")
+ namespace = Namespace.find_by(path: id.first)
+ return nil unless namespace
+
+ where(namespace_id: namespace.id).find_by(path: id.second)
end
def visibility_levels
@@ -181,12 +214,28 @@ class Project < ActiveRecord::Base
id && persisted?
end
+ def add_import_job
+ RepositoryImportWorker.perform_in(2.seconds, id)
+ end
+
def import?
import_url.present?
end
def imported?
- imported
+ import_finished?
+ end
+
+ def import_in_progress?
+ import? && import_status == 'started'
+ end
+
+ def import_failed?
+ import_status == 'failed'
+ end
+
+ def import_finished?
+ import_status == 'finished'
end
def check_limit
@@ -256,7 +305,7 @@ class Project < ActiveRecord::Base
end
def available_services_names
- %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push)
+ %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack)
end
def gitlab_ci?
@@ -340,18 +389,17 @@ class Project < ActiveRecord::Base
branch_name = ref.gsub("refs/heads/", "")
c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
+ # Close merge requests
+ mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
+ mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
+ mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) }
+
# Update code for merge requests into project between project branches
mrs = self.merge_requests.opened.by_branch(branch_name).to_a
# Update code for merge requests between project and project fork
mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
-
mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
- # Close merge requests
- mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
- mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
- mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) }
-
true
end
diff --git a/app/models/project_hook.rb b/app/models/project_hook.rb
index e1c9ed01bc5..4e9b22532cb 100644
--- a/app/models/project_hook.rb
+++ b/app/models/project_hook.rb
@@ -17,9 +17,10 @@
class ProjectHook < WebHook
belongs_to :project
- attr_accessible :push_events, :issues_events, :merge_requests_events
+ attr_accessible :push_events, :issues_events, :merge_requests_events, :tag_push_events
scope :push_hooks, -> { where(push_events: true) }
+ scope :tag_push_hooks, -> { where(tag_push_events: true) }
scope :issue_hooks, -> { where(issues_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
end
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index ad7eade5c7b..2a2c5172916 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -13,6 +13,7 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
class AssemblaService < Service
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index fb2a49fd586..f9247e054c7 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -13,6 +13,7 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
class CampfireService < Service
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index 2a46eff7846..0a453166342 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -13,6 +13,7 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
class EmailsOnPushService < Service
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index f72d9fa9015..2603a1f67a4 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -13,6 +13,7 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
require "flowdock-git-hook"
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
new file mode 100644
index 00000000000..0b8e7bad353
--- /dev/null
+++ b/app/models/project_services/gemnasium_service.rb
@@ -0,0 +1,54 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# token :string(255)
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean default(FALSE), not null
+# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
+# api_key :string(255)
+#
+
+require "gemnasium/gitlab_service"
+
+class GemnasiumService < Service
+ validates :token, :api_key, presence: true, if: :activated?
+
+ def title
+ 'Gemnasium'
+ end
+
+ def description
+ 'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.'
+ end
+
+ def to_param
+ 'gemnasium'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'api_key', placeholder: 'Your personal API KEY on gemnasium.com ' },
+ { type: 'text', name: 'token', placeholder: 'The project\'s slug on gemnasium.com' }
+ ]
+ end
+
+ def execute(push_data)
+ repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
+ Gemnasium::GitlabService.execute(
+ ref: push_data[:ref],
+ before: push_data[:before],
+ after: push_data[:after],
+ token: token,
+ api_key: api_key,
+ repo: repo_path
+ )
+ end
+end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 7f5380a4551..81ac47c913d 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -13,6 +13,7 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
class GitlabCiService < Service
@@ -36,7 +37,7 @@ class GitlabCiService < Service
end
def commit_status sha
- response = HTTParty.get(commit_status_path(sha))
+ response = HTTParty.get(commit_status_path(sha), verify: false)
if response.code == 200 and response["status"]
response["status"]
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index d4795867baf..3cee047a32a 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -13,6 +13,7 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
class HipchatService < Service
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index c5b1b9ab8d3..877b9a77404 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -13,6 +13,7 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
class PivotaltrackerService < Service
diff --git a/app/models/project_services/slack_message.rb b/app/models/project_services/slack_message.rb
new file mode 100644
index 00000000000..b2b8d6fed7a
--- /dev/null
+++ b/app/models/project_services/slack_message.rb
@@ -0,0 +1,95 @@
+require 'slack-notifier'
+
+class SlackMessage
+ def initialize(params)
+ @after = params.fetch(:after)
+ @before = params.fetch(:before)
+ @commits = params.fetch(:commits, [])
+ @project_name = params.fetch(:project_name)
+ @project_url = params.fetch(:project_url)
+ @ref = params.fetch(:ref).gsub('refs/heads/', '')
+ @username = params.fetch(:user_name)
+ end
+
+ def compose
+ format(message)
+ end
+
+ 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
+ elsif removed_branch?
+ removed_branch_message
+ else
+ push_message << commit_messages
+ end
+ end
+
+ def format(string)
+ Slack::Notifier::LinkFormatter.format(string)
+ end
+
+ def new_branch_message
+ "#{username} pushed new branch #{branch_link} to #{project_link}"
+ end
+
+ def removed_branch_message
+ "#{username} removed branch #{ref} from #{project_link}"
+ end
+
+ def push_message
+ "#{username} pushed to branch #{branch_link} of #{project_link} (#{compare_link})"
+ end
+
+ def commit_messages
+ commits.each_with_object('') do |commit, str|
+ str << compose_commit_message(commit)
+ end
+ end
+
+ def compose_commit_message(commit)
+ id = commit.fetch(:id)[0..5]
+ message = commit.fetch(:message)
+ url = commit.fetch(:url)
+
+ "\n - #{message} ([#{id}](#{url}))"
+ end
+
+ def new_branch?
+ before =~ /000000/
+ end
+
+ def removed_branch?
+ after =~ /000000/
+ end
+
+ def branch_url
+ "#{project_url}/commits/#{ref}"
+ end
+
+ def compare_url
+ "#{project_url}/compare/#{before}...#{after}"
+ end
+
+ def branch_link
+ "[#{ref}](#{branch_url})"
+ end
+
+ def project_link
+ "[#{project_name}](#{project_url})"
+ end
+
+ def compare_link
+ "[Compare changes](#{compare_url})"
+ end
+end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
new file mode 100644
index 00000000000..410dda22d47
--- /dev/null
+++ b/app/models/project_services/slack_service.rb
@@ -0,0 +1,68 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# token :string(255)
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean default(FALSE), not null
+# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
+# api_key :string(255)
+#
+
+class SlackService < Service
+ attr_accessible :room
+ attr_accessible :subdomain
+
+ validates :room, presence: true, if: :activated?
+ validates :subdomain, presence: true, if: :activated?
+ validates :token, presence: true, if: :activated?
+
+ def title
+ 'Slack'
+ end
+
+ def description
+ 'A team communication tool for the 21st century'
+ end
+
+ def to_param
+ 'slack'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'subdomain', placeholder: '' },
+ { type: 'text', name: 'token', placeholder: '' },
+ { type: 'text', name: 'room', placeholder: 'Ex. #general' },
+ ]
+ end
+
+ def execute(push_data)
+ message = SlackMessage.new(push_data.merge(
+ project_url: project_url,
+ project_name: project_name
+ ))
+
+ notifier = Slack::Notifier.new(subdomain, token)
+ notifier.channel = room
+ notifier.username = 'GitLab'
+ notifier.ping(message.compose)
+ end
+
+ private
+
+ def project_name
+ project.name_with_namespace.gsub(/\s/, '')
+ end
+
+ def project_url
+ project.web_url
+ end
+end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 2c2bf242b94..35ec84f1651 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -134,6 +134,7 @@ class Repository
Rails.cache.delete(cache_key(:commit_count))
Rails.cache.delete(cache_key(:graph_log))
Rails.cache.delete(cache_key(:readme))
+ Rails.cache.delete(cache_key(:contribution_guide))
end
def graph_log
@@ -167,6 +168,12 @@ class Repository
end
end
+ def contribution_guide
+ Rails.cache.fetch(cache_key(:contribution_guide)) do
+ tree(:head).contribution_guide
+ end
+ end
+
def head_commit
commit(self.root_ref)
end
@@ -180,13 +187,13 @@ class Repository
end
def blob_at_branch(branch_name, path)
- last_commit = commit(branch_name)
+ last_commit = commit(branch_name)
- if last_commit
- blob_at(last_commit.sha, path)
- else
- nil
- end
+ if last_commit
+ blob_at(last_commit.sha, path)
+ else
+ nil
+ end
end
# Returns url for submodule
@@ -196,7 +203,7 @@ class Repository
# # => git@localhost:rack.git
#
def submodule_url_for(ref, path)
- if submodules.any?
+ if submodules(ref).any?
submodule = submodules(ref)[path]
if submodule
diff --git a/app/models/service.rb b/app/models/service.rb
index 540aaad1ce5..f7e440dcc81 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -13,12 +13,15 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
# To add new service you should build a class inherited from Service
# and implement a set of methods
class Service < ActiveRecord::Base
- attr_accessible :title, :token, :type, :active
+ default_value_for :active, false
+
+ attr_accessible :title, :token, :type, :active, :api_key
belongs_to :project
has_one :service_hook
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index edc179b20fd..c1c9ba257f2 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -20,6 +20,8 @@ class Snippet < ActiveRecord::Base
attr_accessible :title, :content, :file_name, :expires_at, :private
+ default_value_for :private, true
+
belongs_to :author, class_name: "User"
has_many :notes, as: :noteable, dependent: :destroy
diff --git a/app/models/tree.rb b/app/models/tree.rb
index 4f866f1a33d..ac2183be44b 100644
--- a/app/models/tree.rb
+++ b/app/models/tree.rb
@@ -1,5 +1,5 @@
class Tree
- attr_accessor :entries, :readme
+ attr_accessor :entries, :readme, :contribution_guide
def initialize(repository, sha, path = '/')
path = '/' if path.blank?
@@ -10,6 +10,11 @@ class Tree
readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name)
@readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path)
end
+
+ if contribution_tree = @entries.find(&:contributing?)
+ contribution_path = path == '/' ? contribution_tree.name : File.join(path, contribution_tree.name)
+ @contribution_guide = Gitlab::Git::Blob.find(git_repo, sha, contribution_path)
+ end
end
def trees
diff --git a/app/models/user.rb b/app/models/user.rb
index 10f21d23506..25c10a6faa0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -78,6 +78,7 @@ class User < ActiveRecord::Base
# Profile
has_many :keys, dependent: :destroy
+ has_many :emails, dependent: :destroy
# Groups
has_many :users_groups, dependent: :destroy
@@ -108,7 +109,7 @@ class User < ActiveRecord::Base
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
- validates :username, presence: true, uniqueness: true,
+ validates :username, presence: true, uniqueness: { case_sensitive: false },
exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.username_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
@@ -116,6 +117,7 @@ class User < ActiveRecord::Base
validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? }
validate :avatar_type, if: ->(user) { user.avatar_changed? }
+ validate :unique_email, if: ->(user) { user.email_changed? }
validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
before_validation :generate_password, on: :create
@@ -184,6 +186,13 @@ class User < ActiveRecord::Base
end
end
+ def find_for_commit(email, name)
+ # Prefer email match over name match
+ User.where(email: email).first ||
+ User.joins(:emails).where(emails: { email: email }).first ||
+ User.where(name: name).first
+ end
+
def filter filter_name
case filter_name
when "admins"; self.admins
@@ -195,7 +204,7 @@ class User < ActiveRecord::Base
end
def search query
- where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%")
+ where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%")
end
def by_username_or_id(name_or_id)
@@ -240,7 +249,7 @@ class User < ActiveRecord::Base
def namespace_uniq
namespace_name = self.username
if Namespace.find_by(path: namespace_name)
- self.errors.add :username, "already exist"
+ self.errors.add :username, "already exists"
end
end
@@ -250,6 +259,10 @@ class User < ActiveRecord::Base
end
end
+ def unique_email
+ self.errors.add(:email, 'has already been taken') if Email.exists?(email: self.email)
+ end
+
# Groups user has access to
def authorized_groups
@authorized_groups ||= begin
@@ -262,7 +275,9 @@ class User < ActiveRecord::Base
# Projects user has access to
def authorized_projects
@authorized_projects ||= begin
- project_ids = (personal_projects.pluck(:id) + groups_projects.pluck(:id) + projects.pluck(:id)).uniq
+ project_ids = personal_projects.pluck(:id)
+ project_ids += groups_projects.pluck(:id)
+ project_ids += projects.pluck(:id).uniq
Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC')
end
end
@@ -393,6 +408,14 @@ class User < ActiveRecord::Base
end
end
+ def requires_ldap_check?
+ if ldap_user?
+ !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
+ else
+ false
+ end
+ end
+
def solo_owned_groups
@solo_owned_groups ||= owned_groups.select do |group|
group.owners == [self]
diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb
index 8a5c4b6cd47..45a795391a2 100644
--- a/app/models/web_hook.rb
+++ b/app/models/web_hook.rb
@@ -17,6 +17,10 @@
class WebHook < ActiveRecord::Base
include HTTParty
+ default_value_for :push_events, true
+ default_value_for :issues_events, false
+ default_value_for :merge_requests_events, false
+
attr_accessible :url
# HTTParty timeout
diff --git a/app/observers/activity_observer.rb b/app/observers/activity_observer.rb
deleted file mode 100644
index d754ac8185a..00000000000
--- a/app/observers/activity_observer.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-class ActivityObserver < BaseObserver
- observe :issue, :note, :milestone
-
- def after_create(record)
- event_author_id = record.author_id
-
- if record.kind_of?(Note)
- # Skip system notes, like status changes and cross-references.
- return true if record.system?
-
- # Skip wall notes to prevent spamming of dashboard
- return true if record.noteable_type.blank?
- end
-
- if event_author_id
- create_event(record, Event.determine_action(record))
- end
- end
-
- def after_close(record, transition)
- create_event(record, Event::CLOSED)
- end
-
- def after_reopen(record, transition)
- create_event(record, Event::REOPENED)
- end
-
- protected
-
- def create_event(record, status)
- Event.create(
- project: record.project,
- target_id: record.id,
- target_type: record.class.name,
- action: status,
- author_id: current_user.id
- )
- end
-end
diff --git a/app/observers/base_observer.rb b/app/observers/base_observer.rb
index f9a0242ce77..d685bd5d819 100644
--- a/app/observers/base_observer.rb
+++ b/app/observers/base_observer.rb
@@ -3,6 +3,10 @@ class BaseObserver < ActiveRecord::Observer
NotificationService.new
end
+ def event_service
+ EventCreateService.new
+ end
+
def log_info message
Gitlab::AppLogger.info message
end
diff --git a/app/observers/email_observer.rb b/app/observers/email_observer.rb
new file mode 100644
index 00000000000..026ad8b1d9a
--- /dev/null
+++ b/app/observers/email_observer.rb
@@ -0,0 +1,5 @@
+class EmailObserver < BaseObserver
+ def after_create(email)
+ notification.new_email(email)
+ end
+end
diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb
index 6ef13eb5d5e..30da1f83da7 100644
--- a/app/observers/issue_observer.rb
+++ b/app/observers/issue_observer.rb
@@ -1,17 +1,20 @@
class IssueObserver < BaseObserver
def after_create(issue)
notification.new_issue(issue, current_user)
+ event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user)
execute_hooks(issue)
end
def after_close(issue, transition)
notification.close_issue(issue, current_user)
+ event_service.close_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
end
def after_reopen(issue, transition)
+ event_service.reopen_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
end
diff --git a/app/observers/merge_request_observer.rb b/app/observers/merge_request_observer.rb
index ef31498e7d0..04ee30b4976 100644
--- a/app/observers/merge_request_observer.rb
+++ b/app/observers/merge_request_observer.rb
@@ -1,27 +1,24 @@
-class MergeRequestObserver < ActivityObserver
- observe :merge_request
-
+class MergeRequestObserver < BaseObserver
def after_create(merge_request)
- if merge_request.author_id
- create_event(merge_request, Event.determine_action(merge_request))
- end
-
+ event_service.open_mr(merge_request, current_user)
notification.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(merge_request.project, current_user)
execute_hooks(merge_request)
end
def after_close(merge_request, transition)
- create_event(merge_request, Event::CLOSED)
+ event_service.close_mr(merge_request, current_user)
notification.close_mr(merge_request, current_user)
create_note(merge_request)
execute_hooks(merge_request)
end
def after_reopen(merge_request, transition)
- create_event(merge_request, Event::REOPENED)
+ event_service.reopen_mr(merge_request, current_user)
create_note(merge_request)
execute_hooks(merge_request)
+ merge_request.reload_code
+ merge_request.mark_as_unchecked
end
def after_update(merge_request)
@@ -31,16 +28,6 @@ class MergeRequestObserver < ActivityObserver
execute_hooks(merge_request)
end
- def create_event(record, status)
- Event.create(
- project: record.target_project,
- target_id: record.id,
- target_type: record.class.name,
- action: status,
- author_id: current_user.id
- )
- end
-
private
# Create merge request note with service comment like 'Status changed to closed'
diff --git a/app/observers/milestone_observer.rb b/app/observers/milestone_observer.rb
new file mode 100644
index 00000000000..a1a62a99a8f
--- /dev/null
+++ b/app/observers/milestone_observer.rb
@@ -0,0 +1,13 @@
+class MilestoneObserver < BaseObserver
+ def after_create(milestone)
+ event_service.open_milestone(milestone, current_user)
+ end
+
+ def after_close(milestone, transition)
+ event_service.close_milestone(milestone, current_user)
+ end
+
+ def after_reopen(milestone, transition)
+ event_service.reopen_milestone(milestone, current_user)
+ end
+end
diff --git a/app/observers/note_observer.rb b/app/observers/note_observer.rb
index d31b6e8d7ce..337bb1dc5ae 100644
--- a/app/observers/note_observer.rb
+++ b/app/observers/note_observer.rb
@@ -2,6 +2,12 @@ class NoteObserver < BaseObserver
def after_create(note)
notification.new_note(note)
+ # Skip system notes, like status changes and cross-references.
+ # Skip wall notes to prevent spamming of dashboard
+ if note.noteable_type.present? && !note.system
+ event_service.leave_note(note, current_user)
+ end
+
unless note.system?
# Create a cross-reference note if this Note contains GFM that names an
# issue, merge request, or commit.
diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb
index 4e3deec01bf..ad41ddad58f 100644
--- a/app/observers/project_observer.rb
+++ b/app/observers/project_observer.rb
@@ -1,30 +1,6 @@
class ProjectObserver < BaseObserver
def after_create(project)
- project.update_column(:last_activity_at, project.created_at)
-
- return true if project.forked?
-
- if project.import?
- RepositoryImportWorker.perform_in(5.seconds, project.id)
- else
- GitlabShellWorker.perform_async(
- :add_repository,
- project.path_with_namespace
- )
-
- log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
- end
-
- if project.wiki_enabled?
- begin
- # force the creation of a wiki,
- GollumWiki.new(project, project.owner).wiki
- rescue GollumWiki::CouldNotCreateWikiError => ex
- # Prevent project observer crash
- # if failed to create wiki
- nil
- end
- end
+ log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
end
def after_update(project)
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
new file mode 100644
index 00000000000..8d8a5873e62
--- /dev/null
+++ b/app/services/event_create_service.rb
@@ -0,0 +1,64 @@
+# EventCreateService class
+#
+# Used for creating events feed on dashboard after certain user action
+#
+# Ex.
+# EventCreateService.new.new_issue(issue, current_user)
+#
+class EventCreateService
+ def open_issue(issue, current_user)
+ create_event(issue, current_user, Event::CREATED)
+ end
+
+ def close_issue(issue, current_user)
+ create_event(issue, current_user, Event::CLOSED)
+ end
+
+ def reopen_issue(issue, current_user)
+ create_event(issue, current_user, Event::REOPENED)
+ end
+
+ def open_mr(merge_request, current_user)
+ create_event(merge_request, current_user, Event::CREATED)
+ end
+
+ def close_mr(merge_request, current_user)
+ create_event(merge_request, current_user, Event::CLOSED)
+ end
+
+ def reopen_mr(merge_request, current_user)
+ create_event(merge_request, current_user, Event::REOPENED)
+ end
+
+ def merge_mr(merge_request, current_user)
+ create_event(merge_request, current_user, Event::MERGED)
+ end
+
+ def open_milestone(milestone, current_user)
+ create_event(milestone, current_user, Event::CREATED)
+ end
+
+ def close_milestone(milestone, current_user)
+ create_event(milestone, current_user, Event::CLOSED)
+ end
+
+ def reopen_milestone(milestone, current_user)
+ create_event(milestone, current_user, Event::REOPENED)
+ end
+
+ def leave_note(note, current_user)
+ create_event(note, current_user, Event::COMMENTED)
+ end
+
+ private
+
+ def create_event(record, current_user, status)
+ Event.create(
+ project: record.project,
+ target_id: record.id,
+ target_type: record.class.name,
+ action: status,
+ author_id: current_user.id
+ )
+ end
+end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index e54f88e42de..fcc03c3e4b8 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -188,8 +188,6 @@ class GitPushService
end
def commit_user commit
- User.where(email: commit.author_email).first ||
- User.where(name: commit.author_name).first ||
- user
+ User.find_for_commit(commit.author_email, commit.author_name) || user
end
end
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
new file mode 100644
index 00000000000..62eaf9b4f51
--- /dev/null
+++ b/app/services/git_tag_push_service.rb
@@ -0,0 +1,40 @@
+class GitTagPushService
+ attr_accessor :project, :user, :push_data
+
+ def execute(project, user, oldrev, newrev, ref)
+ @project, @user = project, user
+ @push_data = create_push_data(oldrev, newrev, ref)
+
+ create_push_event
+ project.repository.expire_cache
+ project.execute_hooks(@push_data.dup, :tag_push_hooks)
+ end
+
+ private
+
+ def create_push_data(oldrev, newrev, ref)
+ data = {
+ ref: ref,
+ before: oldrev,
+ after: newrev,
+ user_id: user.id,
+ user_name: user.name,
+ project_id: project.id,
+ repository: {
+ name: project.name,
+ url: project.url_to_repo,
+ description: project.description,
+ homepage: project.web_url
+ }
+ }
+ end
+
+ def create_push_event
+ Event.create!(
+ project: project,
+ action: Event::PUSHED,
+ data: push_data,
+ author_id: push_data[:user_id]
+ )
+ end
+end
diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb
index d60d61ed54a..e35c03275f2 100644
--- a/app/services/merge_requests/auto_merge_service.rb
+++ b/app/services/merge_requests/auto_merge_service.rb
@@ -9,11 +9,10 @@ module MergeRequests
merge_request.lock
if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
- merge_request.author_id_of_changes = current_user.id
merge_request.merge
- notification.merge_mr(merge_request)
- create_merge_event(merge_request)
+ notification.merge_mr(merge_request, current_user)
+ create_merge_event(merge_request, current_user)
execute_project_hooks(merge_request)
true
diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb
index dbdb0063074..9bc50d3d16c 100644
--- a/app/services/merge_requests/base_merge_service.rb
+++ b/app/services/merge_requests/base_merge_service.rb
@@ -7,14 +7,8 @@ module MergeRequests
NotificationService.new
end
- def create_merge_event(merge_request)
- Event.create(
- project: merge_request.target_project,
- target_id: merge_request.id,
- target_type: merge_request.class.name,
- action: Event::MERGED,
- author_id: merge_request.author_id_of_changes
- )
+ def create_merge_event(merge_request, current_user)
+ EventCreateService.new.merge_mr(merge_request, current_user)
end
def execute_project_hooks(merge_request)
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 1d5af04cdbb..680766140bd 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -7,11 +7,10 @@ module MergeRequests
# to target branch
class MergeService < BaseMergeService
def execute(merge_request, current_user, commit_message)
- merge_request.author_id_of_changes = current_user.id
merge_request.merge
- notification.merge_mr(merge_request)
- create_merge_event(merge_request)
+ notification.merge_mr(merge_request, current_user)
+ create_merge_event(merge_request, current_user)
execute_project_hooks(merge_request)
true
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index fb87e175933..ff6dfb61139 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -3,6 +3,7 @@ module Notes
def execute
note = project.notes.new(params[:note])
note.author = current_user
+ note.system = false
note.save
note
end
diff --git a/app/services/notes/load_service.rb b/app/services/notes/load_service.rb
deleted file mode 100644
index f7ad7d60a3a..00000000000
--- a/app/services/notes/load_service.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module Notes
- class LoadService < BaseService
- def execute
- target_type = params[:target_type]
- target_id = params[:target_id]
-
-
- @notes = case target_type
- when "commit"
- project.notes.for_commit_id(target_id).not_inline.fresh
- when "issue"
- project.issues.find(target_id).notes.inc_author.fresh
- when "merge_request"
- project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh
- when "snippet"
- project.snippets.find(target_id).notes.fresh
- end
- end
- end
-end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 7c02777e914..6fda9868aa5 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -17,6 +17,13 @@ class NotificationService
end
end
+ # Always notify user about email added to profile
+ def new_email(email)
+ if email.user
+ mailer.new_email_email(email.id)
+ end
+ end
+
# When create an issue we should send next emails:
#
# * issue assignee if their notification level is not Disabled
@@ -79,12 +86,12 @@ class NotificationService
# * merge_request assignee if their notification level is not Disabled
# * project team members with notification level higher then Participating
#
- def merge_mr(merge_request)
+ def merge_mr(merge_request, current_user)
recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.target_project)
recipients = recipients.concat(project_watchers(merge_request.target_project)).uniq
recipients.each do |recipient|
- mailer.merged_merge_request_email(recipient.id, merge_request.id)
+ mailer.merged_merge_request_email(recipient.id, merge_request.id, current_user.id)
end
end
@@ -104,6 +111,7 @@ class NotificationService
# ignore gitlab service messages
return true if note.note =~ /\A_Status changed to closed_/
+ return true if note.note =~ /\A_mentioned in / && note.system == true
opts = { noteable_type: note.noteable_type, project_id: note.project_id }
@@ -250,7 +258,7 @@ class NotificationService
recipients.delete(current_user)
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id, target.assignee_id_was)
+ mailer.send(method, recipient.id, target.id, target.assignee_id_was, current_user.id)
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index ba131d8ffbe..4d3d518a509 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -58,6 +58,29 @@ module Projects
user: current_user
)
end
+
+ @project.update_column(:last_activity_at, @project.created_at)
+
+ if @project.import?
+ @project.import_start
+ else
+ GitlabShellWorker.perform_async(
+ :add_repository,
+ @project.path_with_namespace
+ )
+
+ end
+
+ if @project.wiki_enabled?
+ begin
+ # force the creation of a wiki,
+ GollumWiki.new(@project, @project.owner).wiki
+ rescue GollumWiki::CouldNotCreateWikiError => ex
+ # Prevent project observer crash
+ # if failed to create wiki
+ nil
+ end
+ end
end
@project
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index c1130401578..8a1fce17ce7 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -11,17 +11,12 @@ module Search
query = Shellwords.shellescape(query) if query.present?
return result unless query.present?
- authorized_projects_ids = []
- authorized_projects_ids += current_user.authorized_projects.pluck(:id) if current_user
- authorized_projects_ids += Project.public_or_internal_only(current_user).pluck(:id)
-
group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
- projects = Project.where(id: authorized_projects_ids)
+ projects = Project.accessible_to(current_user)
projects = projects.where(namespace_id: group.id) if group
- projects = projects.search(query)
project_ids = projects.pluck(:id)
- result[:projects] = projects.limit(20)
+ result[:projects] = projects.search(query).limit(20)
result[:merge_requests] = MergeRequest.in_projects(project_ids).search(query).order('updated_at DESC').limit(20)
result[:issues] = Issue.where(project_id: project_ids).search(query).order('updated_at DESC').limit(20)
result[:total_results] = %w(projects issues merge_requests).sum { |items| result[items.to_sym].size }
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index e5af56ffc5c..32f77cc1a70 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -14,27 +14,21 @@
%table.table
%thead
%th USER
- %th
%th PID
- %th
%th CPU
- %th
%th MEM
- %th
%th STATE
- %th
%th START
- %th
%th COMMAND
- %th
- - @sidekiq_processes.split("\n").each do |process|
+ %tbody
+ - @sidekiq_processes.each do |process|
- next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
- - data = process.gsub!(/\s+/m, '|').strip.split('|')
+ - data = process.strip.split(' ')
%tr
- - 6.times do
+ %td= Settings.gitlab.user
+ - 5.times do
%td= data.shift
- %td
- %td= data.join(" ")
+ %td= data.join(' ')
.clearfix
%p
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index dd663945ea9..bbd60bc6224 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -3,136 +3,137 @@
%p.light
You can manage projects, users and other GitLab data from here.
%hr
-.admin_dash.row
- .col-sm-4
- .light-well
- %h4 Projects
- .data
- = link_to admin_projects_path do
- %h1= Project.count
- %hr
- = link_to 'New Project', new_project_path, class: "btn btn-new"
- .col-sm-4
- .light-well
- %h4 Users
- .data
- = link_to admin_users_path do
- %h1= User.count
- %hr
- = link_to 'New User', new_admin_user_path, class: "btn btn-new"
- .col-sm-4
- .light-well
- %h4 Groups
- .data
- = link_to admin_groups_path do
- %h1= Group.count
- %hr
- = link_to 'New Group', new_admin_group_path, class: "btn btn-new"
+.admin-dashboard
+ .row
+ .col-sm-4
+ .light-well
+ %h4 Projects
+ .data
+ = link_to admin_projects_path do
+ %h1= Project.count
+ %hr
+ = link_to 'New Project', new_project_path, class: "btn btn-new"
+ .col-sm-4
+ .light-well
+ %h4 Users
+ .data
+ = link_to admin_users_path do
+ %h1= User.count
+ %hr
+ = link_to 'New User', new_admin_user_path, class: "btn btn-new"
+ .col-sm-4
+ .light-well
+ %h4 Groups
+ .data
+ = link_to admin_groups_path do
+ %h1= Group.count
+ %hr
+ = link_to 'New Group', new_admin_group_path, class: "btn btn-new"
-.row.prepend-top-10
- .col-md-4
- %h4 Latest projects
- %hr
- - @projects.each do |project|
+ .row.prepend-top-10
+ .col-md-4
+ %h4 Latest projects
+ %hr
+ - @projects.each do |project|
+ %p
+ = link_to project.name_with_namespace, [:admin, project], class: 'str-truncated'
+ %span.light.pull-right
+ #{time_ago_with_tooltip(project.created_at)}
+
+ .col-md-4
+ %h4 Latest users
+ %hr
+ - @users.each do |user|
+ %p
+ = link_to [:admin, user], class: 'str-truncated' do
+ = user.name
+ %span.light.pull-right
+ #{time_ago_with_tooltip(user.created_at)}
+
+ .col-md-4
+ %h4 Latest groups
+ %hr
+ - @groups.each do |group|
+ %p
+ = link_to [:admin, group], class: 'str-truncated' do
+ = group.name
+ %span.light.pull-right
+ #{time_ago_with_tooltip(group.created_at)}
+
+ %br
+ .row
+ .col-md-4
+ %h4 Stats
+ %hr
%p
- = link_to project.name_with_namespace, [:admin, project]
+ Forks
%span.light.pull-right
- #{time_ago_with_tooltip(project.created_at)}
-
- .col-md-4
- %h4 Latest users
- %hr
- - @users.each do |user|
+ = ForkedProjectLink.count
%p
- = link_to [:admin, user] do
- = user.name
+ Issues
%span.light.pull-right
- #{time_ago_with_tooltip(user.created_at)}
-
- .col-md-4
- %h4 Latest groups
- %hr
- - @groups.each do |group|
+ = Issue.count
%p
- = link_to [:admin, group] do
- = group.name
+ Merge Requests
%span.light.pull-right
- #{time_ago_with_tooltip(group.created_at)}
-
-%br
-.row
- .col-md-4
- %h4 Stats
- %hr
- %p
- Forks
- %span.light.pull-right
- = ForkedProjectLink.count
- %p
- Issues
- %span.light.pull-right
- = Issue.count
- %p
- Merge Requests
- %span.light.pull-right
- = MergeRequest.count
- %p
- Notes
- %span.light.pull-right
- = Note.count
- %p
- Snippets
- %span.light.pull-right
- = Snippet.count
- %p
- SSH Keys
- %span.light.pull-right
- = Key.count
- %p
- Milestones
- %span.light.pull-right
- = Milestone.count
- .col-md-4
- %h4
- Features
- %hr
- %p
- Sign up
- %span.light.pull-right
- = boolean_to_icon gitlab_config.signup_enabled
- %p
- LDAP
- %span.light.pull-right
- = boolean_to_icon Gitlab.config.ldap.enabled
- %p
- Gravatar
- %span.light.pull-right
- = boolean_to_icon Gitlab.config.gravatar.enabled
- %p
- OmniAuth
- %span.light.pull-right
- = boolean_to_icon Gitlab.config.omniauth.enabled
- .col-md-4
- %h4 Components
- %hr
- %p
- GitLab
- %span.pull-right
- = Gitlab::VERSION
- %p
- GitLab Shell
- %span.pull-right
- = Gitlab::Shell.new.version
- %p
- GitLab API
- %span.pull-right
- = API::API::version
- %p
- Ruby
- %span.pull-right
- #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
+ = MergeRequest.count
+ %p
+ Notes
+ %span.light.pull-right
+ = Note.count
+ %p
+ Snippets
+ %span.light.pull-right
+ = Snippet.count
+ %p
+ SSH Keys
+ %span.light.pull-right
+ = Key.count
+ %p
+ Milestones
+ %span.light.pull-right
+ = Milestone.count
+ .col-md-4
+ %h4
+ Features
+ %hr
+ %p
+ Sign up
+ %span.light.pull-right
+ = boolean_to_icon gitlab_config.signup_enabled
+ %p
+ LDAP
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.ldap.enabled
+ %p
+ Gravatar
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.gravatar.enabled
+ %p
+ OmniAuth
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.omniauth.enabled
+ .col-md-4
+ %h4 Components
+ %hr
+ %p
+ GitLab
+ %span.pull-right
+ = Gitlab::VERSION
+ %p
+ GitLab Shell
+ %span.pull-right
+ = Gitlab::Shell.new.version
+ %p
+ GitLab API
+ %span.pull-right
+ = API::API::version
+ %p
+ Ruby
+ %span.pull-right
+ #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
- %p
- Rails
- %span.pull-right
- #{Rails::VERSION::STRING}
+ %p
+ Rails
+ %span.pull-right
+ #{Rails::VERSION::STRING}
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 7a373ee586c..9a0d5967927 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -1,11 +1,12 @@
%h3.page-title
Groups (#{@groups.total_count})
- %small
- allows you to keep projects organized.
- Use groups for uniting related projects.
-
= link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right"
-%br
+
+%p.light
+ Group allows you to keep projects organized.
+ Use groups for uniting related projects.
+
+%hr
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
.form-group
= text_field_tag :name, params[:name], class: "form-control input-mn-300"
@@ -23,24 +24,18 @@
%h4
= link_to [:admin, group] do
+ %i.icon-folder-close
= group.name
&rarr;
%span.monospace
- %i.icon-folder-close
%strong #{group.path}/
-
- .clearfix.light.append-bottom-10
- %span
- %b Members:
- %span.badge= group.members.size
- \|
- %span
- %b Projects:
- %span.badge= group.projects.count
-
.clearfix
%p
= truncate group.description, length: 150
+ .clearfix
+ %p.light
+ #{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')}
+
= paginate @groups, theme: "gitlab"
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 252111875fa..6055865b4cb 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -70,8 +70,9 @@
- @group.users_groups.order('group_access DESC').each do |member|
- user = member.user
%li{class: dom_class(user)}
- %strong
- = link_to user.name, admin_user_path(user)
+ .list-item-name
+ %strong
+ = link_to user.name, admin_user_path(user)
%span.pull-right.light
= member.human_access
= link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
diff --git a/app/views/admin/hooks/_data_ex.html.haml b/app/views/admin/hooks/_data_ex.html.haml
deleted file mode 100644
index 9861e372efc..00000000000
--- a/app/views/admin/hooks/_data_ex.html.haml
+++ /dev/null
@@ -1,67 +0,0 @@
-= highlight_js do
- :erb
- 1. Project created:
- {
- "created_at": "2012-07-21T07:30:54Z",
- "event_name": "project_create",
- "name": "StoreCloud",
- "owner_email": "johnsmith@gmail.com"
- "owner_name": "John Smit",
- "path": "stormcloud",
- "path_with_namespace": "jsmith/stormcloud",
- "project_id": 74,
- }
-
- 2. Project destroyed:
- {
- "created_at": "2012-07-21T07:30:58Z",
- "event_name": "project_destroy",
- "name": "Underscore",
- "owner_email": "johnsmith@gmail.com"
- "owner_name": "John Smith",
- "path": "underscore",
- "path_with_namespace": "jsmith/underscore",
- "project_id": 73,
- }
-
- 3. New Team Member:
- {
- "created_at": "2012-07-21T07:30:56Z",
- "event_name": "user_add_to_team",
- "project_access": "Master",
- "project_id": 74,
- "project_name": "StoreCloud",
- "project_path": "storecloud",
- "user_email": "johnsmith@gmail.com",
- "user_name": "John Smith",
- }
-
- 4. Team Member Removed:
- {
- "created_at": "2012-07-21T07:30:56Z",
- "event_name": "user_remove_from_team",
- "project_access": "Master",
- "project_id": 74,
- "project_name": "StoreCloud",
- "project_path": "storecloud",
- "user_email": "johnsmith@gmail.com",
- "user_name": "John Smith",
- }
-
- 5. User created:
- {
- "created_at": "2012-07-21T07:44:07Z",
- "email": "js@gitlabhq.com",
- "event_name": "user_create",
- "name": "John Smith",
- "user_id": 41
- }
-
- 6. User removed:
- {
- "created_at": "2012-07-21T07:44:07Z",
- "email": "js@gitlabhq.com",
- "event_name": "user_destroy",
- "name": "John Smith",
- "user_id": 41
- }
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index ff90d513ca1..60773c20097 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -28,8 +28,10 @@
%ul.well-list
- @hooks.each do |hook|
%li
+ .list-item-name
+ = link_to admin_hook_path(hook) do
+ %strong= hook.url
+
.pull-right
= link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small"
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
- = link_to admin_hook_path(hook) do
- %strong= hook.url
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 940a539d5e1..296094ab29c 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -44,14 +44,15 @@
%ul.well-list
- @projects.each do |project|
%li
- %span{ class: visibility_level_color(project.visibility_level) }
- = visibility_level_icon(project.visibility_level)
- = link_to project.name_with_namespace, [:admin, project]
+ .list-item-name
+ %span{ class: visibility_level_color(project.visibility_level) }
+ = visibility_level_icon(project.visibility_level)
+ = link_to project.name_with_namespace, [:admin, project]
.pull-right
%span.label.label-gray
= repository_size(project)
= link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
= link_to 'Destroy', [project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-small btn-remove"
- if @projects.blank?
- %p.nothing_here_message 0 projects matches
+ .nothing-here-block 0 projects matches
= paginate @projects, theme: "gitlab"
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 34a91cce163..11f07743ace 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -116,8 +116,9 @@
- @project.users_projects.each do |users_project|
- user = users_project.user
%li.users_project
- %strong
- = link_to user.name, admin_user_path(user)
+ .list-item-name
+ %strong
+ = link_to user.name, admin_user_path(user)
.pull-right
- if users_project.owner?
%span.light Owner
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 1fa6fdfaff1..f42ae7c6a01 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -1,12 +1,6 @@
.row
.col-md-3
.admin-filter
- = form_tag admin_users_path, method: :get, class: 'form-inline' do
- .append-bottom-10
- .form-group
- = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control'
- = button_tag type: 'submit', class: 'btn btn-primary' do
- %i.icon-search
%ul.nav.nav-pills.nav-stacked
%li{class: "#{'active' unless params[:filter]}"}
= link_to admin_users_path do
@@ -25,6 +19,12 @@
Without projects
%small.pull-right= User.without_projects.count
%hr
+ = form_tag admin_users_path, method: :get, class: 'form-inline' do
+ .form-group
+ = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control'
+ = button_tag type: 'submit', class: 'btn btn-primary' do
+ %i.icon-search
+ %hr
= link_to 'Reset', admin_users_path, class: "btn btn-cancel"
.col-md-9
@@ -36,15 +36,16 @@
%ul.well-list
- @users.each do |user|
%li
- - if user.blocked?
- %i.icon-lock.cred
- - else
- %i.icon-user.cgreen
- = link_to user.name, [:admin, user]
- - if user.admin?
- %strong.cred (Admin)
- - if user == current_user
- %span.cred It's you!
+ .list-item-name
+ - if user.blocked?
+ %i.icon-lock.cred
+ - else
+ %i.icon-user.cgreen
+ = link_to user.name, [:admin, user]
+ - if user.admin?
+ %strong.cred (Admin)
+ - if user == current_user
+ %span.cred It's you!
.pull-right
%span.light
%i.icon-envelope
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index e888358dc63..764b34499ab 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -7,7 +7,7 @@
%span.cred (Admin)
.pull-right
- = link_to edit_admin_user_path(@user), class: "btn grouped" do
+ = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do
%i.icon-edit
Edit
%hr
@@ -124,7 +124,8 @@
- @user.users_groups.each do |user_group|
- group = user_group.group
%li.users_group
- %strong= link_to group.name, admin_group_path(group)
+ %span{class: ("list-item-name" unless user_group.owner?)}
+ %strong= link_to group.name, admin_group_path(group)
.pull-right
%span.light= user_group.human_access
- unless user_group.owner?
diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index 39dd600dba3..fdf96dd6f56 100644
--- a/app/views/dashboard/_activities.html.haml
+++ b/app/views/dashboard/_activities.html.haml
@@ -4,6 +4,6 @@
- if @events.any?
.content_list
- else
- %p.nothing_here_message Projects activity will be displayed here
+ .nothing-here-block Projects activity will be displayed here
= spinner
diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml
index 2ff1c33c53a..a6bc946bedc 100644
--- a/app/views/dashboard/_groups.html.haml
+++ b/app/views/dashboard/_groups.html.haml
@@ -17,4 +17,4 @@
%i.icon-angle-right
- if groups.blank?
%li
- %h3.nothing_here_message You have no groups yet.
+ .nothing-here-block You have no groups yet.
diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml
index 8313cc07b5e..44c7a4b8c80 100644
--- a/app/views/dashboard/_projects.html.haml
+++ b/app/views/dashboard/_projects.html.haml
@@ -14,7 +14,7 @@
- if projects.blank?
%li
- %h3.nothing_here_message There are no projects here.
+ .nothing-here-block There are no projects here.
- if @projects_count > @projects_limit
%li.bottom
%span.light
diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml
index 79d5dca8845..e0993293eab 100644
--- a/app/views/dashboard/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/_zero_authorized_projects.html.haml
@@ -29,8 +29,23 @@
%p.slead
You can create a group for several dependent projects.
%br
- Group is the best way to manage projects and members
+ Groups are the best way to manage projects and members
.link_holder
= link_to new_group_path, class: "btn btn-new" do
New group »
+-if @publicish_project_count > 0
+ %hr
+ %div
+ .dashboard-intro-icon
+ %i.icon-globe
+ %div
+ %p.slead
+ There are
+ %strong= @publicish_project_count
+ public projects on this server.
+ %br
+ Public projects are an easy way to allow everyone to have read-only access.
+ .link_holder
+ = link_to public_projects_path, class: "btn btn-new" do
+ Browse public projects »
diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml
index 8feef97c732..e1c9a5941e9 100644
--- a/app/views/dashboard/projects.html.haml
+++ b/app/views/dashboard/projects.html.haml
@@ -67,7 +67,7 @@
- if @projects.blank?
%li
- %h3.nothing_here_message There are no projects here.
+ .nothing-here-block There are no projects here.
.bottom
= paginate @projects, theme: "gitlab"
diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml
index dd63a232fe2..bf634d9de60 100644..100755
--- a/app/views/devise/confirmations/new.html.haml
+++ b/app/views/devise/confirmations/new.html.haml
@@ -3,7 +3,8 @@
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f|
.devise-errors
= devise_error_messages!
- = f.email_field :email, placeholder: 'Email', class: "form-control", required: true
+ .clearfix.append-bottom-20
+ = f.email_field :email, placeholder: 'Email', class: "form-control", required: true
.clearfix.append-bottom-10
= f.submit "Resend confirmation instructions", class: 'btn btn-success'
%hr
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
index a14ef2995c8..040821ca32a 100644..100755
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
@@ -2,7 +2,8 @@
%h3.page-title Reset password
.devise-errors
= devise_error_messages!
- = f.email_field :email, placeholder: "Email", class: "form-control", required: true
+ .clearfix.append-bottom-20
+ = f.email_field :email, placeholder: "Email", class: "form-control", required: true
.clearfix.append-bottom-10
= f.submit "Reset password", class: "btn-primary btn"
%hr
diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/sessions/_oauth_providers.html.haml
index 935bc6af505..2b1cb9c694f 100644
--- a/app/views/devise/sessions/_oauth_providers.html.haml
+++ b/app/views/devise/sessions/_oauth_providers.html.haml
@@ -2,10 +2,12 @@
- if providers.present?
%hr
%div{:'data-no-turbolink' => 'data-no-turbolink'}
- %span Sign in with: &nbsp;
+ %span Sign in with*: &nbsp;
- providers.each do |provider|
%span
- if default_providers.include?(provider)
= link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider)
- else
= link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), class: "btn"
+ %br
+ %small * Make sure your email address is public
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index 938f61d2093..bb87d9ecb4a 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -1,7 +1,7 @@
.login-box
%h3.page-title Sign in
- if ldap_enabled?
- %ul.nav.nav-tabs.append-bottom-20
+ %ul.nav.nav-tabs
%li.active
= link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab'
%li
diff --git a/app/views/groups/_filter.html.haml b/app/views/groups/_filter.html.haml
index 9fbc6c190cc..fe8c0669c0e 100644
--- a/app/views/groups/_filter.html.haml
+++ b/app/views/groups/_filter.html.haml
@@ -21,7 +21,7 @@
= project.name_with_namespace
%small.pull-right= entities_per_project(project, entity)
- if @projects.blank?
- %p.nothing_here_message This group has no projects yet
+ .nothing-here-block This group has no projects yet
%fieldset
%hr
diff --git a/app/views/groups/_new_group_member.html.haml b/app/views/groups/_new_group_member.html.haml
index 5801139a9f2..3ab9276c541 100644
--- a/app/views/groups/_new_group_member.html.haml
+++ b/app/views/groups/_new_group_member.html.haml
@@ -1,15 +1,8 @@
= form_for @users_group, url: group_users_groups_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
- %h4.append-bottom-20
- New member(s) for
- %strong #{@group.name}
- group
-
- %p 1. Choose users you want in the group
.form-group
= f.label :user_ids, "People", class: 'control-label'
.col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large')
- %p 2. Set access level for them
.form-group
= f.label :group_access, "Group Access", class: 'control-label'
.col-sm-10= select_tag :group_access, options_for_select(UsersGroup.group_access_roles, @users_group.group_access), class: "project-access-select select2"
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 029d6cb0efa..bd4e3156af0 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -8,7 +8,7 @@
New project
%ul.well-list
- if projects.blank?
- %p.nothing_here_message This groups has no projects yet
+ .nothing-here-block This groups has no projects yet
- projects.each do |project|
%li.project-row
= link_to project_path(project), class: dom_class(project) do
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index f14fe923402..500c37ab71d 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -73,14 +73,15 @@
%ul.well-list
- @group.projects.each do |project|
%li
- = visibility_level_icon(project.visibility_level)
- = link_to project.name_with_namespace, project
+ .list-item-name
+ = visibility_level_icon(project.visibility_level)
+ = link_to project.name_with_namespace, project
.pull-right
= link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
= 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 @group.projects.blank?
- %p.nothing_here_message This group has no projects yet
+ .nothing-here-block This group has no projects yet
.tab-pane#tab-remove
.ui-box.ui-box-danger
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index e24df310910..4b11d91dc98 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -5,7 +5,9 @@
%p.light
Only issues from
%strong #{@group.name}
- group are listed here. To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
+ group are listed here.
+ - if current_user
+ To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
%hr
.row
diff --git a/app/views/groups/members.html.haml b/app/views/groups/members.html.haml
index 3095a2c7b74..2d0605b18ad 100644
--- a/app/views/groups/members.html.haml
+++ b/app/views/groups/members.html.haml
@@ -1,19 +1,41 @@
+- show_roles = should_user_see_group_roles?(current_user, @group)
%h3.page-title
Group members
-%p.light
- Members of group have access to all group projects.
- Read more about permissions
- %strong= link_to "here", help_permissions_path, class: "vlink"
+- if show_roles
+ %p.light
+ Members of group have access to all group projects.
+ Read more about permissions
+ %strong= link_to "here", help_permissions_path, class: "vlink"
%hr
-.ui-box
+
+.clearfix.js-toggle-container
+ = form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form' do
+ .form-group
+ = search_field_tag :search, params[:search], { placeholder: 'Find member by name', class: 'form-control search-text-input input-mn-300' }
+ = submit_tag 'Search', class: 'btn'
+
+ - if current_user && current_user.can?(:manage_group, @group)
+ .pull-right
+ = link_to '#', class: 'btn btn-new js-toggle-button' do
+ Add members
+ %i.icon-chevron-down
+
+ .js-toggle-content.hide.new-group-member-holder
+ = render "new_group_member"
+
+.ui-box.prepend-top-20
.title
%strong #{@group.name}
group members
%small
- (#{@members.count})
+ (#{@members.total_count})
%ul.well-list
- @members.each do |member|
- = render 'users_groups/users_group', member: member, show_controls: true
-- if current_user.can? :manage_group, @group
- = render "new_group_member"
+ = render 'users_groups/users_group', member: member, show_roles: show_roles, show_controls: true
+= paginate @members, theme: 'gitlab'
+
+:coffeescript
+ $('form.member-search-form').on 'submit', (event) ->
+ event.preventDefault()
+ Turbolinks.visit @.action + '?' + $(@).serialize()
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index eaf85bbdbc8..209130ec444 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -5,7 +5,9 @@
%p.light
Only merge requests from
%strong #{@group.name}
- group are listed here. To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
+ group are listed here.
+ - if current_user
+ To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
%hr
.row
.col-md-3
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 6256c047929..0343670c203 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,16 +1,17 @@
.dashboard
.activities.col-md-8.hidden-sm
- = render "events/event_last_push", event: @last_push
- = link_to dashboard_path, class: 'btn btn-tiny' do
- &larr; To dashboard
- &nbsp;
+ - 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 You will only see events from projects in this group
%hr
= render 'shared/event_filter'
- if @events.any?
.content_list
- else
- %p.nothing_here_message Project activity will be displayed here
+ .nothing-here-block Project activity will be displayed here
= spinner
.side.col-md-4
.light-well.append-bottom-20
@@ -21,11 +22,12 @@
- if @group.description.present?
%p= @group.description
= render "projects", projects: @projects
- .prepend-top-20
- = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do
- %strong
- %i.icon-rss
- News Feed
+ - if current_user
+ .prepend-top-20
+ = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do
+ %strong
+ %i.icon-rss
+ News Feed
%hr
= render 'shared/promo'
diff --git a/app/views/help/_api_layout.html.haml b/app/views/help/_api_layout.html.haml
index c211b658410..af723f906d9 100644
--- a/app/views/help/_api_layout.html.haml
+++ b/app/views/help/_api_layout.html.haml
@@ -5,7 +5,7 @@
%i.icon-angle-left
Back to help
%ul.nav.nav-pills.nav-stacked
- - %w(README projects project_snippets repositories deploy_keys users groups session issues milestones merge_requests notes system_hooks).each do |file|
+ - %w(README projects project_snippets repositories repository_files commits deploy_keys users groups session issues milestones merge_requests notes system_hooks).each do |file|
%li{class: file == @category ? 'active' : nil}
= link_to file.titleize, help_api_file_path(file)
diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml
index 7fd0c74057d..5d3ee4526fe 100644
--- a/app/views/help/permissions.html.haml
+++ b/app/views/help/permissions.html.haml
@@ -1,220 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title Permissions
- %p.light Users have different abilities depending on the access level they have in particular group or project.
- %p.light If a user is both in a project group and in the project itself the highest permission level is used.
- %p.light If a user is a GitLab administrator they receive all permissions.
- %hr
+ %h3.page-title Permissions
- %h4 Project:
- %table.table
- %thead
- %tr
- %th Action
- %th Guest
- %th Reporter
- %th Developer
- %th Master
- %th Owner
- %tbody
- %tr
- %td Create new issue
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Leave comments
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Write on project wall
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Pull project code
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Download project
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Create code snippets
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Create new merge request
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Create new branches
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Push to non-protected branches
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Remove non-protected branches
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Add tags
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Write a wiki
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Manage issue tracker
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Add new team members
- %td
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Push to protected branches
- %td
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Remove protected branches
- %td
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Edit project
- %td
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Add Deploy Keys to project
- %td
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Configure Project Hooks
- %td
- %td
- %td
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Switch visibility level
- %td
- %td
- %td
- %td
- %td.permission-x &#10003;
- %tr
- %td Transfer project to another namespace
- %td
- %td
- %td
- %td
- %td.permission-x &#10003;
- %tr
- %td Remove project
- %td
- %td
- %td
- %td
- %td.permission-x &#10003;
-
- %h4 Group
- %table.table
- %thead
- %tr
- %th Action
- %th Guest
- %th Reporter
- %th Developer
- %th Master
- %th Owner
- %tbody
- %tr
- %td Browse group
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %td.permission-x &#10003;
- %tr
- %td Edit group
- %td
- %td
- %td
- %td
- %td.permission-x &#10003;
- %tr
- %td Create project in group
- %td
- %td
- %td
- %td
- %td.permission-x &#10003;
- %tr
- %td Manage group members
- %td
- %td
- %td
- %td
- %td.permission-x &#10003;
- %tr
- %td Remove group
- %td
- %td
- %td
- %td
- %td.permission-x &#10003;
- %p.light Any user can remove himself from a group, unless he is the last Owner of the group.
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "permissions", "permissions.md"))
diff --git a/app/views/help/public_access.html.haml b/app/views/help/public_access.html.haml
index e1b2a2ce1fa..d1ec3f5d5c1 100644
--- a/app/views/help/public_access.html.haml
+++ b/app/views/help/public_access.html.haml
@@ -1,61 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title Public Access
+ %h3.page-title Public Access
- %p.slead
- GitLab allows you to open selected projects to be accessed
- %strong publicly
- or
- %strong internally
- \.
- %br
- Projects with either of these visibility levels will be listed in the #{link_to "public access directory", public_root_path}.
- %br
- Internal projects will only be available to authenticated users.
-
- .clearfix
- .dashboard-intro-icon
- = public_icon
- %h4
- Public projects
- %p
- Public project can be cloned
- %strong without any
- authentication.
- %br
- It will also be listed on the #{link_to "public access directory", public_root_path}.
- %br
- %strong Any logged in user
- will have #{link_to "Guest", help_permissions_path} permissions on the repository.
-
- .clearfix
- .dashboard-intro-icon
- = internal_icon
- %h4
- Internal projects
- %p
- Internal project can be cloned by any logged in user.
- %br
- It will also be listed on the #{link_to "public access directory", public_root_path} for logged in users.
- %br
- Any logged in user will have #{link_to "Guest", help_permissions_path} permissions on the repository.
-
- %h4 How to change project visibility
- %ol
- %li Go to your project dashboard
- %li Click on the "Edit" tab
- %li Change "Visibility Level"
-
- %h4 Visibility of users
- The public page of users, located at
- = succeed "," do
- %code u/username
- is visible if either:
- %ul
- %li
- You are logged in.
- %li
- %p
- You are logged out, and the target user is authorized to (is Guest, Reporter, etc.)
- at least one public project.
- %p Otherwise, you will be redirected to the sign in page.
- When visiting the public page of an user, you will only see listed projects which you can view yourself.
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "public_access", "public_access.md"))
diff --git a/app/views/help/ssh.html.haml b/app/views/help/ssh.html.haml
index 1c7b08aa7cd..8ae1ed1ab6e 100644
--- a/app/views/help/ssh.html.haml
+++ b/app/views/help/ssh.html.haml
@@ -1,28 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title SSH Keys
+ %h3.page-title SSH Keys
- %p.slead
- SSH key allows you to establish a secure connection between your computer and GitLab
-
- %p.slead
- Before generating an SSH key, check if your system already has one by running cat ~/.ssh/id_rsa.pub
- If your see a long string starting with 'ssh-rsa' or 'ssh-dsa', you can skip the ssh-keygen step.
-
- %p.slead
- To generate a new SSH key just open your terminal and use code below. The ssh-keygen command prompts you for a location and filename to store the key pair and for a password.
- When prompted for the location and filename you can press enter to use the default.
- It is a best practice to use a password for an SSH key but it is not required and you can skip creating a password by pressing enter.
- Note that the password you choose here can't be altered or retrieved.
-
- %pre.dark
- ssh-keygen -t rsa -C "#{current_user.email}"
-
- %p.slead
- Use code below to show your public key.
-
- %pre.dark
- cat ~/.ssh/id_rsa.pub
-
- %p.slead
- Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your user profile.
- Please copy the complete key starting with 'ssh-' and ending with your username and host.
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "ssh", "ssh.md"))
diff --git a/app/views/help/system_hooks.html.haml b/app/views/help/system_hooks.html.haml
index 3e401a6e19f..e24f566121d 100644
--- a/app/views/help/system_hooks.html.haml
+++ b/app/views/help/system_hooks.html.haml
@@ -1,11 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title System hooks
+ %h3.page-title System hooks
- %p.slead
- Your GitLab instance can perform HTTP POST requests on the following events: create_project, delete_project, create_user, delete_user, change_team_member.
- %br
- %br
- System Hooks can be used, e.g. for logging or changing information in a LDAP server.
- %br
- %h5 Hooks request example:
- = render "admin/hooks/data_ex"
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "system_hooks", "system_hooks.md"))
diff --git a/app/views/help/web_hooks.html.haml b/app/views/help/web_hooks.html.haml
index 7bde7fcc3d0..9e2b54ab6ec 100644
--- a/app/views/help/web_hooks.html.haml
+++ b/app/views/help/web_hooks.html.haml
@@ -1,115 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title Project web hooks
- %p.light
- Project web hooks allow you to trigger url if new code is pushed or new issue is created
- %hr
+ %h3.page-title Project web hooks
- %p.slead
- You can configure web hook to listen for specific events like pushes, issues, merge requests.
- %br
- GitLab will send POST request with data to web hook url.
- %br
- Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
- %hr
-
- %h4 Push events
- %p.light
- Triggered when you push to the repository except pushing tags.
- %br
- Request body:
- = highlight_js do
- :erb
- {
- "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
- "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
- "ref": "refs/heads/master",
- "user_id": 4,
- "user_name": "John Smith",
- "project_id": 15,
- "repository": {
- "name": "Diaspora",
- "url": "git@localhost:diaspora.git",
- "description": "",
- "homepage": "http://localhost/diaspora",
- },
- "commits": [
- {
- "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
- "message": "Update Catalan translation to e38cb41.",
- "timestamp": "2011-12-12T14:27:31+02:00",
- "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
- "author": {
- "name": "Jordi Mallach",
- "email": "jordi@softcatala.org",
- }
- },
- // ...
- {
- "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
- "message": "fixed readme",
- "timestamp": "2012-01-03T23:36:29+02:00",
- "url": "http://localhost/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
- "author": {
- "name": "GitLab dev user",
- "email": "gitlabdev@dv6700.(none)",
- },
- },
- ],
- "total_commits_count": 4,
- };
-
-
- %h4.prepend-top-20 Issues events
- %p.light
- Triggered when new issue created or existing issue was updated/closed/reopened.
- %br
- Request body:
- = highlight_js do
- :erb
- {
- "object_kind":"issue",
- "object_attributes":{
- "id":301,
- "title":"New API: create/update/delete file",
- "assignee_id":51,
- "author_id":51,
- "project_id":14,
- "created_at":"2013-12-03T17:15:43Z",
- "updated_at":"2013-12-03T17:15:43Z",
- "position":0,
- "branch_name":null,
- "description":"Create new API for manipulations with repository",
- "milestone_id":null,
- "state":"opened",
- "iid":23
- }
- }
- %h4.prepend-top-20 Merge request events
- %p.light
- Triggered when new merge request created or existing merge request was updated/merged/closed.
- %br
- Request body:
- = highlight_js do
- :erb
- {
- "object_kind":"merge_request",
- "object_attributes":{
- "id":99,
- "target_branch":"master",
- "source_branch":"ms-viewport",
- "source_project_id":14,
- "author_id":51,
- "assignee_id":6,
- "title":"MS-Viewport",
- "created_at":"2013-12-03T17:23:34Z",
- "updated_at":"2013-12-03T17:23:34Z",
- "st_commits":null,
- "st_diffs":null,
- "milestone_id":null,
- "state":"opened",
- "merge_status":"unchecked",
- "target_project_id":14,
- "iid":1,
- "description":""
- }
- }
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "web_hooks", "web_hooks.md"))
diff --git a/app/views/help/workflow.html.haml b/app/views/help/workflow.html.haml
index 2b8950cd5c2..47de4ad796c 100644
--- a/app/views/help/workflow.html.haml
+++ b/app/views/help/workflow.html.haml
@@ -1,37 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title Workflow
-
- %ol.help
- %li
- %p Clone project
- .bash
- %pre.dark
- git clone git@example.com:project-name.git
-
- %li
- %p Create branch with your feature
- .bash
- %pre.dark
- git checkout -b $feature_name
-
- %li
- %p Write code. Commit changes
- .bash
- %pre.dark
- git commit -am "My feature is ready"
-
- %li
- %p Push your branch to GitLab
- .bash
- %pre.dark
- git push origin $feature_name
-
- %li
- %p Review your code on Commits page
-
- %li
- %p Create a merge request
-
- %li
- %p Your team lead will review code &amp; merge it to main branch
+ %h3.page-title Workflow
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "workflow", "workflow.md"))
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 5723250151a..d9a7a2d31cf 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -4,7 +4,8 @@
= "#{title} | " if defined?(title)
GitLab
= favicon_link_tag 'favicon.ico'
- = stylesheet_link_tag "application"
+ = stylesheet_link_tag "application", :media => "all"
+ = stylesheet_link_tag "print", :media => "print"
= javascript_include_tag "application"
= csrf_meta_tags
= include_gon
@@ -12,6 +13,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
+ = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
-# Atom feed
- if current_user
diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml
index 5080a1b7ef6..d8001fd76d7 100644
--- a/app/views/layouts/_head_panel.html.haml
+++ b/app/views/layouts/_head_panel.html.haml
@@ -15,10 +15,6 @@
.navbar-collapse.collapse
%ul.nav.navbar-nav
%li.hidden-sm.hidden-xs
- %a
- %div.hide.turbolink-spinner
- %i.icon-refresh.icon-spin
- %li.hidden-sm.hidden-xs
= render "layouts/search"
%li.visible-sm.visible-xs
= link_to search_path, title: "Search", class: 'has_bottom_tooltip', 'data-original-title' => 'Search area' do
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 6a20dedf62f..353f7ce34f1 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -1,4 +1,3 @@
:javascript
- GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_project_path(@project)}"
- GitLab.GfmAutoComplete.Emoji.assetBase = "#{Gitlab.config.gitlab.relative_url_root + '/assets/emoji'}"
+ GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_project_path(@project, type: @noteable.class, type_id: params[:id])}"
GitLab.GfmAutoComplete.setup();
diff --git a/app/views/layouts/_piwik.html.haml b/app/views/layouts/_piwik.html.haml
new file mode 100644
index 00000000000..135e8daca26
--- /dev/null
+++ b/app/views/layouts/_piwik.html.haml
@@ -0,0 +1,12 @@
+:javascript
+ var _paq = _paq || [];
+ _paq.push(["trackPageView"]);
+ _paq.push(["enableLinkTracking"]);
+
+ (function() {
+ var u=(("https:" == document.location.protocol) ? "https" : "http") + "://#{extra_config.piwik_url}/";
+ _paq.push(["setTrackerUrl", u+"piwik.php"]);
+ _paq.push(["setSiteId", "#{extra_config.piwik_site_id}"]);
+ var d=document, g=d.createElement("script"), s=d.getElementsByTagName("script")[0]; g.type="text/javascript";
+ g.defer=true; g.async=true; g.src=u+"piwik.js"; s.parentNode.insertBefore(g,s);
+ })();
diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml
index 99e11cd2b5c..63992a22f32 100644
--- a/app/views/layouts/_public_head_panel.html.haml
+++ b/app/views/layouts/_public_head_panel.html.haml
@@ -8,11 +8,15 @@
%span.separator
%h1.title= title
- .pull-right
+ %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
+ %span.sr-only Toggle navigation
+ %i.icon-reorder
+
+ .pull-right.hidden-xs
= link_to "Sign in", new_session_path(:user), class: 'btn btn-sign-in btn-new'
- %ul.nav.navbar-nav
- %li
- %a
- %div.hide.turbolink-spinner
- %i.icon-refresh.icon-spin
+ .navbar-collapse.collapse
+ %ul.nav.navbar-nav
+ %li.visible-xs
+ = link_to "Sign in", new_session_path(:user)
+
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 439cb978a76..53e0dbaef9b 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en"}
= render "layouts/head", title: "Admin area"
%body{class: "#{app_theme} admin", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: "Admin area"
= render "layouts/flash"
%nav.main-nav.navbar-collapse.collapse
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index a8bb3b91559..36b102dc25a 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -5,11 +5,13 @@
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group) do
Issues
- %span.count= current_user.assigned_issues.opened.of_group(@group).count
+ - if current_user
+ %span.count= current_user.assigned_issues.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group) do
Merge Requests
- %span.count= current_user.cared_merge_requests.opened.of_group(@group).count
+ - if current_user
+ %span.count= current_user.cared_merge_requests.opened.of_group(@group).count
= nav_link(path: 'groups#members') do
= link_to "Members", members_group_path(@group)
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index d44cb975ea5..35d0d417502 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -4,6 +4,10 @@
%i.icon-home
= nav_link(controller: :accounts) do
= link_to "Account", profile_account_path
+ = nav_link(controller: :emails) do
+ = link_to profile_emails_path do
+ 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
diff --git a/app/views/layouts/navless.html.haml b/app/views/layouts/navless.html.haml
index 7325664bc19..c43d688a2cb 100644
--- a/app/views/layouts/navless.html.haml
+++ b/app/views/layouts/navless.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en"}
= render "layouts/head", title: @title
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: @title
= render "layouts/flash"
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index f88abeca887..09d84a3eb9f 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -3,20 +3,24 @@
%meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
%title
GitLab
+ :css
+ p.details {
+ font-style:italic;
+ color:#777
+ }
+ .footer p {
+ font-size:small;
+ color:#777
+ }
%body
- %h1{style: "background: #EEE; border-bottom: 1px solid #DDD; color: #474D57; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
- GitLab
- - if @project
- \|
- = link_to @project.name_with_namespace, project_url(@project), style: 'color: #29B; text-decoration: none'
- %table{align: "left", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 10px 0;", width: "100%"}
- %tr
- %td{align: "left", style: "margin: 0; padding: 10px;"}
- = yield
- %br
- %tr
- %td{align: "left", style: "margin: 0; padding: 10px;"}
- %p{style: "font-size:small;color:#777"}
- - if @project
- You're receiving this notification because you are a member of the #{@project.name_with_namespace} project team.
+ %div.content
+ = yield
+ %div.footer{style: "margin-top: 10px;"}
+ %p
+ \—
+ %br
+ - if @project
+ You're receiving this notification because you are a member of the #{link_to @project.name_with_namespace, project_url(@project)} project team.
+ - if @target_url
+ #{link_to "View in GitLab", @target_url}
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index fda81b3cc83..3c76bbb9575 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en"}
= render "layouts/head", title: "Public Projects"
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
- if current_user
= render "layouts/head_panel", title: "Public Projects"
- else
diff --git a/app/views/layouts/public_group.html.haml b/app/views/layouts/public_group.html.haml
new file mode 100644
index 00000000000..a289b784725
--- /dev/null
+++ b/app/views/layouts/public_group.html.haml
@@ -0,0 +1,10 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head", title: group_head_title
+ %body{class: "#{app_theme} 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
diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml
index 7de2347803a..2a9230244f8 100644
--- a/app/views/layouts/public_projects.html.haml
+++ b/app/views/layouts/public_projects.html.haml
@@ -2,8 +2,9 @@
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
- = render "layouts/public_head_panel", title: @project.name_with_namespace
- %nav.main-nav
+ = 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
diff --git a/app/views/layouts/public_users.html.haml b/app/views/layouts/public_users.html.haml
index 80709d650da..4aa258fea0d 100644
--- a/app/views/layouts/public_users.html.haml
+++ b/app/views/layouts/public_users.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en"}
= render "layouts/head", title: @title
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
= render "layouts/public_head_panel", title: @title
.container.navless-container
.content= yield
diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml
index 177b3a4f8f4..97ed8ba12df 100644
--- a/app/views/layouts/search.html.haml
+++ b/app/views/layouts/search.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en"}
= render "layouts/head", title: "Search"
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: "Search"
= render "layouts/flash"
diff --git a/app/views/layouts/user_team.html.haml b/app/views/layouts/user_team.html.haml
index 191ad406c3c..ce13853ed7f 100644
--- a/app/views/layouts/user_team.html.haml
+++ b/app/views/layouts/user_team.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en"}
= render "layouts/head", title: "#{@team.name}"
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: "team: #{@team.name}"
= render "layouts/flash"
%nav.main-nav.navbar-collapse.collapse
diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml
index 9e329af2d47..5272dfa0ede 100644
--- a/app/views/notify/_note_message.html.haml
+++ b/app/views/notify/_note_message.html.haml
@@ -1,6 +1,2 @@
-%p
- %strong #{@note.author_name}
- wrote:
-
-%cite{style: 'color: #666'}
+%div
= markdown(@note.note)
diff --git a/app/views/notify/closed_issue_email.html.haml b/app/views/notify/closed_issue_email.html.haml
index 325cd44eb4b..56c18cd83cd 100644
--- a/app/views/notify/closed_issue_email.html.haml
+++ b/app/views/notify/closed_issue_email.html.haml
@@ -1,5 +1,2 @@
%p
= "Issue was closed by #{@updated_by.name}"
-%p
- = "Issue ##{@issue.iid}"
- = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml
index 45770cc85de..809d46f31be 100644
--- a/app/views/notify/closed_merge_request_email.html.haml
+++ b/app/views/notify/closed_merge_request_email.html.haml
@@ -1,9 +1,2 @@
%p
- = "Merge Request #{@merge_request.iid} was closed by #{@updated_by.name}"
-%p
- = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
-%p
- != merge_path_description(@merge_request, '&rarr;')
-%p
- Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
-
+ = "Merge Request !#{@merge_request.iid} was closed by #{@updated_by.name}"
diff --git a/app/views/notify/group_access_granted_email.html.haml b/app/views/notify/group_access_granted_email.html.haml
index 5023ec737a5..823ebf77347 100644
--- a/app/views/notify/group_access_granted_email.html.haml
+++ b/app/views/notify/group_access_granted_email.html.haml
@@ -1,5 +1,4 @@
%p
= "You have been granted #{@membership.human_access} access to group"
-%p
= link_to group_url(@group) do
= @group.name
diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml
index 7706b3f7516..482c884a9db 100644
--- a/app/views/notify/issue_status_changed_email.html.haml
+++ b/app/views/notify/issue_status_changed_email.html.haml
@@ -1,5 +1,2 @@
%p
= "Issue was #{@issue_status} by #{@updated_by.name}"
-%p
- = "Issue ##{@issue.iid}"
- = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml
index e2bc9cf5c04..0c62d439aed 100644
--- a/app/views/notify/merged_merge_request_email.html.haml
+++ b/app/views/notify/merged_merge_request_email.html.haml
@@ -1,9 +1,2 @@
%p
- = "Merge Request #{@merge_request.iid} was merged"
-%p
- = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
-%p
- != merge_path_description(@merge_request, '&rarr;')
-%p
- Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
-
+ = "Merge Request !#{@merge_request.iid} was merged"
diff --git a/app/views/notify/new_email_email.html.haml b/app/views/notify/new_email_email.html.haml
new file mode 100644
index 00000000000..4a0448a573c
--- /dev/null
+++ b/app/views/notify/new_email_email.html.haml
@@ -0,0 +1,10 @@
+%p
+ Hi #{@user.name}!
+%p
+ A new email was added to your account:
+%p
+ email:
+ %code= @email.email
+%p
+ If this email was added in error, you can remove it here:
+ = link_to "Emails", profile_emails_url
diff --git a/app/views/notify/new_email_email.text.erb b/app/views/notify/new_email_email.text.erb
new file mode 100644
index 00000000000..51cba99ad0d
--- /dev/null
+++ b/app/views/notify/new_email_email.text.erb
@@ -0,0 +1,7 @@
+Hi <%= @user.name %>!
+
+A new email was added to your account:
+
+email.................. <%= @email.email %>
+
+If this email was added in error, you can remove it here: <%= profile_emails_url %>
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index 3b3c148517a..f2f8eee18c4 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -1,9 +1,6 @@
-%p
- New Issue was created.
-%p
- = "Issue ##{@issue.iid}"
- = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
-%p
- Author: #{@issue.author_name}
-%p
- Assignee: #{@issue.assignee_name}
+-if @issue.description
+ = markdown(@issue.description)
+
+- if @issue.assignee_id.present?
+ %p
+ Assignee: #{@issue.assignee_name}
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index 321f9418ded..f02d5111b22 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -1,9 +1,9 @@
-%p
- = "New Merge Request ##{@merge_request.iid}"
-%p
- = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
-%p
+%p.details
!= merge_path_description(@merge_request, '&rarr;')
-%p
- Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
+- if @merge_request.assignee_id.present?
+ %p
+ Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
+
+-if @merge_request.description
+ = markdown(@merge_request.description)
diff --git a/app/views/notify/note_commit_email.html.haml b/app/views/notify/note_commit_email.html.haml
index 620b258fc15..1d961e4424c 100644
--- a/app/views/notify/note_commit_email.html.haml
+++ b/app/views/notify/note_commit_email.html.haml
@@ -1,5 +1,2 @@
-%p
- = "New comment for Commit #{@commit.short_id}"
- = link_to_gfm truncate(@commit.title, length: 16), project_commit_url(@note.project, id: @commit.id, anchor: "note_#{@note.id}")
= render 'note_message'
diff --git a/app/views/notify/note_issue_email.html.haml b/app/views/notify/note_issue_email.html.haml
index b3230953e7d..2fa2f784661 100644
--- a/app/views/notify/note_issue_email.html.haml
+++ b/app/views/notify/note_issue_email.html.haml
@@ -1,4 +1 @@
-%p
- = "New comment for Issue ##{@issue.iid}"
- = link_to_gfm truncate(@issue.title, length: 35), project_issue_url(@issue.project, @issue, anchor: "note_#{@note.id}")
= render 'note_message'
diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml
index d587b068486..65f0e4c4068 100644
--- a/app/views/notify/note_merge_request_email.html.haml
+++ b/app/views/notify/note_merge_request_email.html.haml
@@ -1,8 +1,7 @@
-%p
- - if @note.for_diff_line?
- = link_to "New comment on diff", diffs_project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")
- - else
- = link_to "New comment", project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")
- for Merge Request ##{@merge_request.iid}
- %cite "#{truncate(@merge_request.title, length: 20)}"
+- if @note.diff_file_name
+ %p.details
+ New comment on diff for
+ = link_to @note.diff_file_name, @target_url
+ \:
+
= render 'note_message'
diff --git a/app/views/notify/note_wall_email.html.haml b/app/views/notify/note_wall_email.html.haml
index 92200e83efa..2fa2f784661 100644
--- a/app/views/notify/note_wall_email.html.haml
+++ b/app/views/notify/note_wall_email.html.haml
@@ -1,5 +1 @@
-%p
- New message on
- = link_to "Project Wall", project_wall_url(@note.project, anchor: "note_#{@note.id}")
-
= render 'note_message'
diff --git a/app/views/notify/reassigned_issue_email.html.haml b/app/views/notify/reassigned_issue_email.html.haml
index b0edff44ce6..07227a3e68c 100644
--- a/app/views/notify/reassigned_issue_email.html.haml
+++ b/app/views/notify/reassigned_issue_email.html.haml
@@ -1,7 +1,4 @@
%p
- = "Reassigned Issue ##{@issue.iid}"
- = link_to_gfm truncate(@issue.title, length: 30), project_issue_url(@issue.project, @issue)
-%p
Assignee changed
- if @previous_assignee
from
diff --git a/app/views/notify/reassigned_merge_request_email.html.haml b/app/views/notify/reassigned_merge_request_email.html.haml
index d2d82d36c48..00aee6bc952 100644
--- a/app/views/notify/reassigned_merge_request_email.html.haml
+++ b/app/views/notify/reassigned_merge_request_email.html.haml
@@ -1,7 +1,4 @@
%p
- = "Reassigned Merge Request ##{@merge_request.iid}"
- = link_to_gfm truncate(@merge_request.title, length: 30), project_merge_request_url(@merge_request.target_project, @merge_request)
-%p
Assignee changed
- if @previous_assignee
from
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index ab0d6c653b9..85a01a567f3 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -7,7 +7,7 @@
%li
#{commit.short_id} - #{commit.title}
-%h4 Diff:
+%h4 Changes:
- @diffs.each do |diff|
%li
%strong
@@ -23,6 +23,6 @@
%br
- if @compare.timeout
- %h5 Huge diff. To prevent performance issues it was hidden
+ %h5 To prevent performance issues changes are hidden
- elsif @compare.commits_over_limit?
- %h5 Diff for big amount of commits is disabled
+ %h5 Changes are not shown due to large amount of commits
diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml
index 93b344d2c82..b8d7fbeb046 100644
--- a/app/views/notify/repository_push_email.text.haml
+++ b/app/views/notify/repository_push_email.text.haml
@@ -6,7 +6,7 @@ Commits:
#{commit.short_id} - #{truncate(commit.title, length: 40)}
\
\
-Diff:
+Changes:
- @diffs.each do |diff|
\
\=====================================
@@ -22,4 +22,4 @@ Diff:
- if @compare.timeout
Huge diff. To prevent performance issues it was hidden
- elsif @compare.commits_over_limit?
- Diff for big amount of commits is disabled
+ Changes are not shown due to large amount of commits
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
new file mode 100644
index 00000000000..b5f1e438ccb
--- /dev/null
+++ b/app/views/profiles/emails/index.html.haml
@@ -0,0 +1,33 @@
+%h3.page-title
+ My email addresses
+%p.light
+ Your
+ %b Primary Email
+ will be used for account notifications, avatar detection and web based operations, such as edits and merges.
+ %br
+ All email addresses will be used to identify your commits.
+
+%hr
+
+.ui-box
+ .title
+ Emails (#{@emails.count + 1})
+ %ul.well-list#emails-table
+ %li
+ %strong= @primary
+ %span.label.label-success Primary Email
+ - @emails.each do |email|
+ %li
+ %strong= email.email
+ %span.cgray
+ added #{time_ago_with_tooltip(email.created_at)}
+ = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-small btn-remove pull-right'
+
+%h4 Add email address
+= form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f|
+ .form-group
+ = f.label :email, class: 'control-label'
+ .col-sm-10
+ = f.text_field :email, class: 'form-control'
+ .form-actions
+ = f.submit 'Add', class: 'btn btn-create'
diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml
index eefebf98c51..cebc0b6bccb 100644
--- a/app/views/profiles/groups/index.html.haml
+++ b/app/views/profiles/groups/index.html.haml
@@ -18,12 +18,12 @@
%li
.pull-right
- if can?(current_user, :manage_group, group)
- = link_to edit_group_path(group), class: "btn-small btn grouped" do
+ = link_to edit_group_path(group), class: "btn-small btn btn-grouped" do
%i.icon-cogs
Settings
- if can?(current_user, :destroy, user_group)
- = link_to leave_profile_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-small btn grouped", title: 'Remove user from group' do
+ = link_to leave_profile_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-small btn btn-grouped", title: 'Remove user from group' do
%i.icon-signout
Leave
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 79fdb164089..71a4ca91d42 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -17,6 +17,6 @@
= render @keys
- if @keys.blank?
%li
- %h3.nothing_here_message There are no SSH keys with access to your account.
+ .nothing-here-block There are no SSH keys with access to your account.
diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml
index d123b8f9407..218d51d31af 100644
--- a/app/views/profiles/notifications/_settings.html.haml
+++ b/app/views/profiles/notifications/_settings.html.haml
@@ -1,31 +1,17 @@
%li
- .row
- .col-sm-4
- %span
- = notification_icon(notification)
-
- - if membership.kind_of? UsersGroup
- = link_to membership.group.name, membership.group
- - else
- = link_to_project(membership.project)
- .col-sm-8
- = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
- = hidden_field_tag :notification_type, type, id: dom_id(membership, 'notification_type')
- = hidden_field_tag :notification_id, membership.id, id: dom_id(membership, 'notification_id')
-
- = label_tag nil, class: 'radio-inline' do
- = radio_button_tag :notification_level, Notification::N_GLOBAL, notification.global?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit'
- %span Use global setting
-
- = label_tag nil, class: 'radio-inline' do
- = radio_button_tag :notification_level, Notification::N_DISABLED, notification.disabled?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit'
- %span Disabled
-
- = label_tag nil, class: 'radio-inline' do
- = radio_button_tag :notification_level, Notification::N_PARTICIPATING, notification.participating?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit'
- %span Participating
-
- = label_tag nil, class: 'radio-inline' do
- = radio_button_tag :notification_level, Notification::N_WATCH, notification.watch?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit'
- %span Watch
+ %span.notification-icon-holder
+ - if notification.global?
+ = notification_icon(@notification)
+ - else
+ = notification_icon(notification)
+ %span.str-truncated
+ - if membership.kind_of? UsersGroup
+ = link_to membership.group.name, membership.group
+ - else
+ = link_to_project(membership.project)
+ .pull-right
+ = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
+ = hidden_field_tag :notification_type, type, id: dom_id(membership, 'notification_type')
+ = hidden_field_tag :notification_id, membership.id, id: dom_id(membership, 'notification_id')
+ = select_tag :notification_level, options_for_select(Notification.options_with_labels, notification.level), class: 'trigger-submit'
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 878d7f77430..efe9c032190 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -3,56 +3,49 @@
%p.light
GitLab uses the email specified in your profile for notifications
%hr
-.alert.alert-info
- %p
- %i.icon-circle.cred
- %strong Disabled
- &ndash; You will not get any notifications via email
- %p
- %i.icon-circle.cblue
- %strong Participating
- &ndash; You will only receive notifications from related resources (e.g. from your commits or assigned issues)
- %p
- %i.icon-circle.cgreen
- %strong Watch
- &ndash; You will receive all notifications from projects in which you participate
+= form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications form-horizontal global-notifications-form' do
+ = hidden_field_tag :notification_type, 'global'
-.row
- .col-sm-4
- %h4
- = notification_icon(@notification)
- Global setting
- .col-sm-8
- = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
- = hidden_field_tag :notification_type, 'global'
-
- = label_tag nil, class: 'radio-inline' do
+ = label_tag :notification_level, 'Notification level', class: 'control-label'
+ .col-sm-10
+ .radio
+ = label_tag nil, class: '' do
= radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit'
- %span Disabled
+ .level-title
+ Disabled
+ %p You will not get any notifications via email
- = label_tag nil, class: 'radio-inline' do
+ .radio
+ = label_tag nil, class: '' do
= radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit'
- %span Participating
+ .level-title
+ Participating
+ %p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
- = label_tag nil, class: 'radio-inline' do
+ .radio
+ = label_tag nil, class: '' do
= radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit'
- %span Watch
+ .level-title
+ Watch
+ %p You will receive all notifications from projects in which you participate
-%br
-= link_to '#', class: 'js-toggle-visibility-link' do
- %span.btn.btn-tiny
- %i.icon-chevron-down
- %span Advanced notifications settings
-.js-toggle-visibility-container.hide
+.clearfix
%hr
- %h4 Groups:
- %ul.bordered-list
- - @users_groups.each do |users_group|
- - notification = Notification.new(users_group)
- = render 'settings', type: 'group', membership: users_group, notification: notification
+ %p
+ You can also specify notification level per group or per project
+ %br
+ By default all projects and groups uses notification level set above
+.row.all-notifications
+ .col-md-6
+ %h4 Groups:
+ %ul.bordered-list
+ - @users_groups.each do |users_group|
+ - notification = Notification.new(users_group)
+ = render 'settings', type: 'group', membership: users_group, notification: notification
- %h4 Projects:
- %ul.bordered-list
- - @users_projects.each do |users_project|
- - notification = Notification.new(users_project)
- = render 'settings', type: 'project', membership: users_project, notification: notification
+ .col-md-6
+ %h4 Projects:
+ %ul.bordered-list
+ - @users_projects.each do |users_project|
+ - notification = Notification.new(users_project)
+ = render 'settings', type: 'project', membership: users_project, notification: notification
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index c8c0368d000..b72232ee36b 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -2,9 +2,9 @@
%hr
= form_for @user, url: profile_password_path, method: :post, html: { class: 'form-horizontal '} do |f|
%p.slead
- Please set new password before proceed.
+ Please set a new password before proceeding.
%br
- After successful password update you will be redirected to login screen
+ After a successful password update you will be redirected to login screen.
-if @user.errors.any?
.alert.alert-danger
%ul
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index 080a39ab944..7cbea7c3eb6 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -10,4 +10,4 @@
- unless blob.empty?
= render 'shared/file_hljs', blob: blob
- else
- %p.nothing_here_message Empty file
+ .nothing-here-block Empty file
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 40b6fc5d72e..f0731977098 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -10,13 +10,14 @@
%i.icon-lock
.pull-right
- if can?(current_user, :download_code, @project)
- = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'grouped btn-group-small'
- = link_to project_compare_index_path(@project, from: branch.name, to: branch.name), class: 'btn grouped btn-small', title: "Compare" do
- %i.icon-copy
- Compare
+ = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-small'
+ - if branch.name != @repository.root_ref
+ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-small', method: :post, title: "Compare" do
+ %i.icon-copy
+ Compare
- if can?(current_user, :admin_project, @project) && branch.name != @repository.root_ref
- = link_to project_branch_path(@project, branch.name), class: 'btn grouped btn-small remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
+ = link_to project_branch_path(@project, branch.name), class: 'btn btn-grouped btn-small remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
%i.icon-trash
- if commit
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 690df98a2ab..bee04eb013e 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -4,7 +4,7 @@
= render "filter"
.col-md-9
- unless @branches.empty?
- %ul.bordered-list.top-list
+ %ul.bordered-list.top-list.all-branches
- @branches.each do |branch|
= render "projects/branches/branch", branch: branch
= paginate @branches, theme: 'gitlab'
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 3d666807cf9..f2b0699f136 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,18 +1,18 @@
.pull-right
%div
- if @notes_count > 0
- %span.btn.disabled.grouped
+ %span.btn.disabled.btn-grouped
%i.icon-comment
= @notes_count
.pull-left.btn-group
- %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} }
+ %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
%i.icon-download-alt
Download as
%span.caret
%ul.dropdown-menu
%li= link_to "Email Patches", project_commit_path(@project, @commit, format: :patch)
%li= link_to "Plain Diff", project_commit_path(@project, @commit, format: :diff)
- = link_to project_tree_path(@project, @commit), class: "btn btn-primary grouped" do
+ = link_to project_tree_path(@project, @commit), class: "btn btn-primary btn-grouped" do
%span Browse Code »
%div
@@ -47,7 +47,7 @@
- if @branches.any?
and in
= link_to("#{pluralize(@branches.count, "other branch")}", "#", class: "js-details-expand")
- %span.js-details-contain.hide
+ %span.js-details-content.hide
= commit_branches_links(@project, @branches)
.commit-box
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 9772d3ef2ef..a0606662807 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -1,9 +1,12 @@
-%li.commit
+%li.commit.js-toggle-container
.commit-row-title
= link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
&nbsp;
%span.str-truncated
= link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
+ - if commit.description?
+ %a.text-expander.js-toggle-button ...
+
= link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right"
.notes_count
- notes = project.notes.for_commit_id(commit.id)
@@ -12,6 +15,10 @@
%i.icon-comment
= notes.count
+ - if commit.description?
+ .commit-row-description.js-toggle-content
+ = simple_format(commit.description)
+
.commit-row-info
= commit_author_link(commit, avatar: true, size: 16)
.committed_ago
diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml
index a41a89bb972..466085139f9 100644
--- a/app/views/projects/commits/_diffs.html.haml
+++ b/app/views/projects/commits/_diffs.html.haml
@@ -8,18 +8,18 @@
- if current_controller?(:commit) or current_controller?(:merge_requests)
Please, download the diff as
- if current_controller?(:commit)
- = link_to "plain diff", project_commit_path(@project, @commit, format: :diff), class: "underlined_link"
+ = link_to "plain diff", project_commit_path(@project, @commit, format: :diff), class: "underlined-link"
or
- = link_to "email patch", project_commit_path(@project, @commit, format: :patch), class: "underlined_link"
+ = link_to "email patch", project_commit_path(@project, @commit, format: :patch), class: "underlined-link"
- else
- = link_to "plain diff", project_merge_request_path(@project, @merge_request, format: :diff), class: "underlined_link"
+ = link_to "plain diff", project_merge_request_path(@project, @merge_request, format: :diff), class: "underlined-link"
or
- = link_to "email patch", project_merge_request_path(@project, @merge_request, format: :patch), class: "underlined_link"
+ = link_to "email patch", project_merge_request_path(@project, @merge_request, format: :patch), class: "underlined-link"
instead.
- unless @force_suppress_diff
%p
If you still want to see the diff
- = link_to "click this link", url_for(force_show_diff: true), class: "underlined_link"
+ = link_to "click this link", url_for(force_show_diff: true), class: "underlined-link"
%p.commit-stat-summary
Showing
@@ -44,25 +44,38 @@
- file = project.repository.blob_at(@commit.id, diff.new_path)
- file = project.repository.blob_at(@commit.parent_id, diff.old_path) unless file
- next unless file
- .file{id: "diff-#{i}"}
- .header
+ .diff-file.js-toggle-container{id: "diff-#{i}"}
+ .diff-header{id: "file-path-#{hexdigest(diff.new_path || diff.old_path)}"}
- if diff.deleted_file
%span= diff.old_path
- - if @commit.parent_ids.present?
- = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), { class: 'btn btn-small view-file' } do
- View file @
- %span.commit-short-id= @commit.short_id(6)
+ .diff-btn-group
+ - if @commit.parent_ids.present?
+ = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), { class: 'btn btn-small view-file' } do
+ View file @
+ %span.commit-short-id= @commit.short_id(6)
- else
%span= diff.new_path
- if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
%span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
- = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), { class: 'btn btn-small view-file' } do
- View file @
- %span.commit-short-id= @commit.short_id(6)
+ .diff-btn-group
+ = link_to "#", class: "js-toggle-button btn btn-small" do
+ %i.icon-chevron-down
+ Diff comments
+ &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.new_path), from_merge_request_id: @merge_request.id), { class: 'btn btn-small' } do
+ Edit
+ &nbsp;
+
+ = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), { class: 'btn btn-small view-file' } do
+ View file @
+ %span.commit-short-id= @commit.short_id(6)
+
- .content
+ .diff-content
-# Skipp all non non-supported blobs
- next unless file.respond_to?('text?')
- if file.text?
@@ -74,4 +87,4 @@
- old_file = project.repository.blob_at(@commit.parent_id, diff.old_path) if @commit.parent_id
= render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i
- else
- %p.nothing_here_message No preview for this file type
+ .nothing-here-block No preview for this file type
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index b9ab27d212c..81e33743911 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,4 +1,4 @@
-%ul.nav.nav-tabs.append-bottom-15
+%ul.nav.nav-tabs
%li= render partial: 'shared/ref_switcher', locals: {destination: 'commits'}
= nav_link(controller: [:commit, :commits]) do
diff --git a/app/views/projects/commits/_image.html.haml b/app/views/projects/commits/_image.html.haml
index 9a8b7c857e5..6d9ef5964d9 100644
--- a/app/views/projects/commits/_image.html.haml
+++ b/app/views/projects/commits/_image.html.haml
@@ -15,7 +15,7 @@
%span.meta-filesize= "#{number_to_human_size old_file.size}"
|
%b W:
- %span.meta-width
+ %span.meta-width
|
%b H:
%span.meta-height
@@ -27,7 +27,7 @@
%span.meta-filesize= "#{number_to_human_size file.size}"
|
%b W:
- %span.meta-width
+ %span.meta-width
|
%b H:
%span.meta-height
@@ -49,7 +49,7 @@
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
.frame.added
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
- .col-sm-10
+ .controls
.transparent
.drag-track
.dragger{:style => "left: 0px;"}
diff --git a/app/views/projects/commits/_parallel_view.html.haml b/app/views/projects/commits/_parallel_view.html.haml
index 3234e9da0ac..5b60ab80ba4 100644
--- a/app/views/projects/commits/_parallel_view.html.haml
+++ b/app/views/projects/commits/_parallel_view.html.haml
@@ -1,75 +1,55 @@
/ Side-by-side diff view
-- old_file = get_old_file(project, @commit, diff)
-- deleted_lines = {}
-- added_lines = {}
-- each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line|
- - if type == "old"
- - deleted_lines[line_old] = { line_code: line_code, type: type, line: line }
- - elsif type == "new"
- - added_lines[line_new] = { line_code: line_code, type: type, line: line }
-
-- max_length = old_file.sloc + added_lines.length if old_file
-- max_length ||= file.sloc
-- offset1 = 0
-- offset2 = 0
+- old_lines, new_lines = parallel_diff_lines(project, @commit, diff, file)
+- num_lines = old_lines.length
%div.text-file-parallel
- %table{ style: "table-layout: fixed;" }
- - max_length.times do |line_index|
- - line_index1 = line_index - offset1
- - line_index2 = line_index - offset2
- - deleted_line = deleted_lines[line_index1 + 1]
- - added_line = added_lines[line_index2 + 1]
- - old_line = old_file.lines[line_index1] if old_file
- - new_line = file.lines[line_index2]
+ %div.diff-side.diff-side-left
+ %table
+ - old_lines.each do |line|
+
+ %tr.line_holder.parallel
+ - if line.type == :file_created
+ %td.line_content.parallel= "File was created"
+ - elsif line.type == :deleted
+ %td.line_content{class: "parallel noteable_line old #{line.code}", "line_code" => line.code }= line.content
+ - else line.type == :no_change
+ %td.line_content.parallel= line.content
+
+ %div.diff-middle
+ %table
+ - num_lines.times do |index|
+ %tr
+ - if old_lines[index].type == :deleted
+ %td.old_line.old= old_lines[index].num
+ - else
+ %td.old_line= old_lines[index].num
+
+ %td.diff_line=""
- - if deleted_line && added_line
- - elsif deleted_line
- - new_line = nil
- - offset2 += 1
- - elsif added_line
- - old_line = nil
- - offset1 += 1
+ - if new_lines[index].type == :added
+ %td.new_line.new= new_lines[index].num
+ - else
+ %td.new_line= new_lines[index].num
- %tr.line_holder.parallel
- - if line_index == 0 && diff.new_file
- %td.line_content.parallel= "File was created"
- %td.old_line= ""
- - elsif deleted_line
- %td.line_content{class: "parallel noteable_line old #{deleted_line[:line_code]}", "line_code" => deleted_line[:line_code] }= old_line
- %td.old_line.old
- = line_index1 + 1
- - if @comments_allowed
- =# render "projects/notes/diff_note_link", line_code: deleted_line[:line_code]
- - elsif old_line
- %td.line_content.parallel= old_line
- %td.old_line= line_index1 + 1
- - else
- %td.line_content.parallel= ""
- %td.old_line= ""
+ %div.diff-side.diff-side-right
+ %table
+ - new_lines.each do |line|
- %td.diff_line= ""
+ %tr.line_holder.parallel
+ - if line.type == :file_deleted
+ %td.line_content.parallel= "File was deleted"
+ - elsif line.type == :added
+ %td.line_content{class: "parallel noteable_line new #{line.code}", "line_code" => line.code }= line.content
+ - else line.type == :no_change
+ %td.line_content.parallel= line.content
- - if diff.deleted_file && line_index == 0
- %td.new_line= ""
- %td.line_content.parallel= "File was deleted"
- - elsif added_line
- %td.new_line.new
- = line_index2 + 1
- - if @comments_allowed
- =# render "projects/notes/diff_note_link", line_code: added_line[:line_code]
- %td.line_content{class: "parallel noteable_line new #{added_line[:line_code]}", "line_code" => added_line[:line_code] }= new_line
- - elsif new_line
- %td.new_line= line_index2 + 1
- %td.line_content.parallel= new_line
- - else
- %td.new_line= ""
- %td.line_content.parallel= ""
+:javascript
+ $('.diff-side-right').on('scroll', function(){
+ $('.diff-side-left, .diff-middle').scrollTop($(this).scrollTop());
+ $('.diff-side-left').scrollLeft($(this).scrollLeft());
+ });
- - if @reply_allowed
- - comments1 = []
- - comments2 = []
- - comments1 = @line_notes.select { |n| n.line_code == deleted_line[:line_code] }.sort_by(&:created_at) if deleted_line
- - comments2 = @line_notes.select { |n| n.line_code == added_line[:line_code] }.sort_by(&:created_at) if added_line
- - unless comments1.empty? && comments2.empty?
- = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments1, notes2: comments2, line1: deleted_line, line2: added_line \ No newline at end of file
+ $('.diff-side-left').on('scroll', function(){
+ $('.diff-side-right, .diff-middle').scrollTop($(this).scrollTop()); // might never be relevant
+ $('.diff-side-right').scrollLeft($(this).scrollLeft());
+ });
diff --git a/app/views/projects/commits/_text_file.html.haml b/app/views/projects/commits/_text_file.html.haml
index c827d96d855..ba83d2e5a0f 100644
--- a/app/views/projects/commits/_text_file.html.haml
+++ b/app/views/projects/commits/_text_file.html.haml
@@ -1,6 +1,6 @@
- too_big = diff.diff.lines.count > 1000
- if too_big
- %a.supp_diff_link Diff suppressed. Click to show
+ %a.supp_diff_link Changes suppressed. Click to show
%table.text-file{class: "#{'hide' if too_big}"}
- each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line|
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 9bd49855369..57331bff31b 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -18,17 +18,17 @@
- else
%ul.well-list= render Commit.decorate(@commits), project: @project
- %h4 Diff
+ %h4 Changes
- if @diffs.present?
= render "projects/commits/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 diff is not shown.
+ %p To preserve performance the line changes are not shown.
- elsif @timeout
.bs-callout.bs-callout-danger
- %h4 Diff for this comparison is extremely large.
- %p Use command line to browse diff for this comparison.
+ %h4 Number of changed files for this comparison is extremely large.
+ %p Use command line to browse through changes for this comparison.
- else
diff --git a/app/views/projects/create.js.haml b/app/views/projects/create.js.haml
index a444b8b59a6..89710d3a09a 100644
--- a/app/views/projects/create.js.haml
+++ b/app/views/projects/create.js.haml
@@ -1,6 +1,10 @@
- if @project.saved?
- :plain
- location.href = "#{project_path(@project)}";
+ - if @project.import?
+ :plain
+ location.href = "#{import_project_path(@project)}";
+ - else
+ :plain
+ location.href = "#{project_path(@project)}";
- else
:plain
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
index 90d86102aca..f50aeba337a 100644
--- a/app/views/projects/deploy_keys/index.html.haml
+++ b/app/views/projects/deploy_keys/index.html.haml
@@ -20,7 +20,7 @@
= render @enabled_keys
- if @enabled_keys.blank?
.light-well
- %p.nothing_here_message Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one
+ .nothing-here-block Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one
.col-md-6.available-keys
%h5
%strong Deploy keys
@@ -29,4 +29,4 @@
= render @available_keys
- if @available_keys.blank?
.light-well
- %p.nothing_here_message Deploy keys from projects you have access to will be displayed here
+ .nothing-here-block Deploy keys from projects you have access to will be displayed here
diff --git a/app/views/projects/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml
index 67615e1781f..c66e6bc69c3 100644
--- a/app/views/projects/deploy_keys/show.html.haml
+++ b/app/views/projects/deploy_keys/show.html.haml
@@ -2,7 +2,7 @@
Deploy key:
= @key.title
%small
- created at
+ created on
= @key.created_at.stamp("Aug 21, 2011")
.back-link
= link_to project_deploy_keys_path(@project) do
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index b9cd5a20d50..10674ccae46 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -93,99 +93,101 @@
- %center.light.prepend-top-20.padded
- %h3
- %i.icon-warning-sign
- Dangerous settings
- %p Project settings below may result in data loss!
- = link_to '#', class: 'btn js-toggle-visibility-link' do
- Show it to me
- %i.icon-chevron-down
-
- .js-toggle-visibility-container.hide
- - if can? current_user, :archive_project, @project
- .ui-box.ui-box-danger
- .title
- - if @project.archived?
- Unarchive project
- - else
- Archive project
- .body
- - if @project.archived?
- %p
- Unarchiving the project will mark its repository as active.
- %br
- The project can be committed to.
- %br
- %strong Once active this project shows up in the search and on the dashboard.
- = link_to 'Unarchive', unarchive_project_path(@project),
- data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be comitted to again." },
- method: :post, class: "btn btn-remove"
- - else
- %p
- Archiving the project will mark its repository as read-only.
- %br
- It is hidden from the dashboard and doesn't show up in searches.
- %br
- %strong Archived projects cannot be committed to!
- = link_to 'Archive', archive_project_path(@project),
- data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
- method: :post, class: "btn btn-remove"
- - else
- %p.nothing_here_message Only the project owner can archive a project
-
- - if can?(current_user, :change_namespace, @project)
+ .danger-settings.js-toggle-container
+ .centered-light-block
+ %h3
+ %i.icon-warning-sign
+ Dangerous settings
+
+ %p Project settings below may result in data loss!
+ = link_to '#', class: 'btn js-toggle-button' do
+ Show it to me
+ %i.icon-chevron-down
+
+ .js-toggle-content.hide
+ - if can? current_user, :archive_project, @project
+ .ui-box.ui-box-danger
+ .title
+ - if @project.archived?
+ Unarchive project
+ - else
+ Archive project
+ .body
+ - if @project.archived?
+ %p
+ Unarchiving the project will mark its repository as active.
+ %br
+ The project can be committed to.
+ %br
+ %strong Once active this project shows up in the search and on the dashboard.
+ = link_to 'Unarchive', unarchive_project_path(@project),
+ data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be comitted to again." },
+ method: :post, class: "btn btn-remove"
+ - else
+ %p
+ Archiving the project will mark its repository as read-only.
+ %br
+ It is hidden from the dashboard and doesn't show up in searches.
+ %br
+ %strong Archived projects cannot be committed to!
+ = link_to 'Archive', archive_project_path(@project),
+ data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
+ method: :post, class: "btn btn-remove"
+ - else
+ .nothing-here-block Only the project owner can archive a project
+
+ - if can?(current_user, :change_namespace, @project)
+ .ui-box.ui-box-danger
+ .title Transfer project
+ .errors-holder
+ .form-holder
+ = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
+ .form-group
+ = f.label :namespace_id, class: 'control-label' do
+ %span Namespace
+ .col-sm-10
+ .form-group
+ = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' }
+ %ul
+ %li Be careful. Changing the project's namespace can have unintended side effects.
+ %li You can only transfer the project to namespaces you manage.
+ %li You will need to update your local repositories to point to the new location.
+ .form-actions
+ = f.submit 'Transfer', class: "btn btn-remove"
+ - else
+ .nothing-here-block Only the project owner can transfer a project
+
.ui-box.ui-box-danger
- .title Transfer project
+ .title Rename repository
.errors-holder
.form-holder
- = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
+ = form_for(@project, html: { class: 'form-horizontal' }) do |f|
.form-group
- = f.label :namespace_id, class: 'control-label' do
- %span Namespace
- .col-sm-10
+ = f.label :path, class: 'control-label' do
+ %span Path
+ .col-sm-9
.form-group
- = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' }
+ .input-group
+ = f.text_field :path, class: 'form-control'
+ %span.input-group-addon .git
%ul
- %li Be careful. Changing the project's namespace can have unintended side effects.
- %li You can only transfer the project to namespaces you manage.
+ %li Be careful. Renaming a project's repository can have unintended side effects.
%li You will need to update your local repositories to point to the new location.
.form-actions
- = f.submit 'Transfer', class: "btn btn-remove"
- - else
- %p.nothing_here_message Only the project owner can transfer a project
-
- .ui-box.ui-box-danger
- .title Rename repository
- .errors-holder
- .form-holder
- = form_for(@project, html: { class: 'form-horizontal' }) do |f|
- .form-group
- = f.label :path, class: 'control-label' do
- %span Path
- .col-sm-9
- .form-group
- .input-group
- = f.text_field :path, class: 'form-control'
- %span.input-group-addon .git
- %ul
- %li Be careful. Renaming a project's repository can have unintended side effects.
- %li You will need to update your local repositories to point to the new location.
- .form-actions
- = f.submit 'Rename', class: "btn btn-remove"
-
- - if can?(current_user, :remove_project, @project)
- .ui-box.ui-box-danger
- .title Remove project
- .body
- %p
- Removing the project will delete its repository and all related resources including issues, merge requests etc.
- %br
- %strong Removed projects cannot be restored!
-
- = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove"
- - else
- %p.nothing_here_message Only project owner can remove a project
+ = f.submit 'Rename', class: "btn btn-remove"
+
+ - if can?(current_user, :remove_project, @project)
+ .ui-box.ui-box-danger
+ .title Remove project
+ .body
+ %p
+ Removing the project will delete its repository and all related resources including issues, merge requests etc.
+ %br
+ %strong Removed projects cannot be restored!
+
+ = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove"
+ - else
+ .nothing-here-block Only project owner can remove a project
.save-project-loader.hide
%center
diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml
index 433131640a1..3f2e98f3a7f 100644
--- a/app/views/projects/edit_tree/show.html.haml
+++ b/app/views/projects/edit_tree/show.html.haml
@@ -11,7 +11,7 @@
%strong= @ref
%span.options
.btn-group.tree-btn-group
- = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message }
+ = link_to "Cancel", @after_edit_path, class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message }
.file-content.code
%pre#editor= @blob.data
@@ -23,12 +23,13 @@
.form-actions
= hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content"
+ = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
.commit-button-annotation
= button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-primary'
.message
to branch
%strong= @ref
- = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message}
+ = link_to "Cancel", @after_edit_path, class: "btn btn-cancel", data: { confirm: leave_edit_message}
:javascript
ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace")
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 489b9b0e951..97dc73bce14 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,46 +1,34 @@
= render "home_panel"
-- if @project.import? && !@project.imported
- .save-project-loader
- %center
- %h2
- %i.icon-spinner.icon-spin
- Importing repository.
- %p.monospace git clone --bare #{@project.import_url}
- %p Please wait while we import the repository for you. Refresh at will.
- :javascript
- new ProjectImport();
+%div.git-empty
+ %fieldset
+ %legend Git global setup:
+ %pre.dark
+ :preserve
+ git config --global user.name "#{git_user_name}"
+ git config --global user.email "#{git_user_email}"
-- else
- %div.git-empty
- %fieldset
- %legend Git global setup:
- %pre.dark
- :preserve
- git config --global user.name "#{git_user_name}"
- git config --global user.email "#{git_user_email}"
+ %fieldset
+ %legend Create Repository
+ %pre.dark
+ :preserve
+ mkdir #{@project.path}
+ cd #{@project.path}
+ git init
+ touch README
+ git add README
+ git commit -m 'first commit'
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git push -u origin master
- %fieldset
- %legend Create Repository
- %pre.dark
- :preserve
- mkdir #{@project.path}
- cd #{@project.path}
- git init
- touch README
- git add README
- git commit -m 'first commit'
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- git push -u origin master
+ %fieldset
+ %legend Existing Git Repo?
+ %pre.dark
+ :preserve
+ cd existing_git_repo
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git push -u origin master
- %fieldset
- %legend Existing Git Repo?
- %pre.dark
- :preserve
- cd existing_git_repo
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- git push -u origin master
-
- - if can? current_user, :remove_project, @project
- .prepend-top-20
- = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
+- if can? current_user, :remove_project, @project
+ .prepend-top-20
+ = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index a095fd06d2f..866fd6f6066 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -27,6 +27,13 @@
%p.light
This url will be triggered by a push to the repository
%div
+ = f.check_box :tag_push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :tag_push_events, class: 'list-label' do
+ %strong Tag push events
+ %p.light
+ This url will be triggered when a new tag is pushed to the repository
+ %div
= f.check_box :issues_events, class: 'pull-left'
.prepend-left-20
= f.label :issues_events, class: 'list-label' do
@@ -51,11 +58,11 @@
- @hooks.each do |hook|
%li
.pull-right
- = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small grouped"
- = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small grouped"
+ = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small btn-grouped"
+ = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small btn-grouped"
.clearfix
%span.monospace= hook.url
%p
- - %w(push_events issues_events merge_requests_events).each do |trigger|
+ - %w(push_events tag_push_events issues_events merge_requests_events).each do |trigger|
- if hook.send(trigger)
%span.label.label-gray= trigger.titleize
diff --git a/app/views/projects/import.html.haml b/app/views/projects/import.html.haml
new file mode 100644
index 00000000000..d11372be61b
--- /dev/null
+++ b/app/views/projects/import.html.haml
@@ -0,0 +1,30 @@
+- if @project.import_in_progress?
+ .save-project-loader
+ %center
+ %h2
+ %i.icon-spinner.icon-spin
+ Import in progress.
+ %p.monospace git clone --bare #{@project.import_url}
+ %p Please wait while we import the repository for you. Refresh at will.
+ :javascript
+ new ProjectImport();
+
+- elsif @project.import_failed?
+ .save-project-loader
+ %center
+ %h2
+ Import failed. Retry?
+ %hr
+ - if can?(current_user, :admin_project, @project)
+ = form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
+ .form-group.import-url-data
+ = f.label :import_url, class: 'control-label' do
+ %span Import existing repo
+ .col-sm-10
+ = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
+ .bs-callout.bs-callout-info
+ This url must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
+ %br
+ The import will time out after 2 minutes. For big repositories, use a clone/push combination.
+ .form-actions
+ = f.submit 'Retry import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index c95e8178594..05cae80e50c 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,6 +1,10 @@
%div.issue-form-holder
%h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}"
%hr
+ - if !@repository.empty? && @repository.contribution_guide && !@issue.persisted?
+ - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
+ .alert.alert-info.col-sm-10.col-sm-offset-2
+ ="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' } do |f|
-if @issue.errors.any?
.alert.alert-danger
@@ -24,7 +28,7 @@
%i.icon-user
Assign to
.col-sm-10
- = f.select(:assignee_id, assignee_options(@issue), { include_blank: "Select a user" }, {class: 'select2'})
+ = project_users_select_tag('issue[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @issue.assignee_id)
&nbsp;
= link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
.form-group
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index 61213e752f8..0b7697622b0 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -1,4 +1,4 @@
-%ul.nav.nav-tabs.append-bottom-15
+%ul.nav.nav-tabs
= nav_link(controller: :issues) do
= link_to project_issues_path(@project), class: "tab" do
Browse Issues
@@ -17,10 +17,10 @@
%li.pull-right
.pull-right
- = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'inline issue-search-form' do
+ = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
.append-right-10.hidden-xs.hidden-sm
= search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
- if can? current_user, :write_issue, @project
- = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
+ = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
%i.icon-plus
New Issue
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 1bd93b774f1..3fc04c26cf2 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -38,10 +38,10 @@
.issue-actions
- if can? current_user, :modify_issue, issue
- if issue.closed?
- = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small grouped reopen_issue", remote: true
+ = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue", remote: true
- else
- = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small grouped close_issue", remote: true
- = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link grouped" do
+ = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue", remote: true
+ = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link btn-grouped" do
%i.icon-edit
Edit
diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml
index 7ddf470b6a0..aae101cf40f 100644
--- a/app/views/projects/issues/_issue_context.html.haml
+++ b/app/views/projects/issues/_issue_context.html.haml
@@ -1,25 +1,22 @@
= form_for [@project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f|
- Created by #{link_to_member(@project, issue.author)}&nbsp;
- - if issue.assignee
- \ and currently assigned to
+ %strong.append-right-10
+ Assignee:
- if can?(current_user, :modify_issue, @issue)
- = link_to profile_path(issue.assignee) do
- = image_tag(avatar_icon(issue.assignee.email), class: 'avatar avatar-inline s16 assignee') if issue.assignee
- = f.select(:assignee_id, assignee_options(@issue), { include_blank: "Assign to user (none):" }, {class: 'select2'})
+ = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @issue.assignee_id)
- elsif issue.assignee
= link_to_member(@project, @issue.assignee)
+ - else
+ None
-
- .pull-right.hidden-sm
- - if issue.milestone
- - milestone = issue.milestone
- %cite.cgray Attached to milestone
-
+ .pull-right
+ %strong.append-right-10
+ Milestone:
- if can?(current_user, :modify_issue, @issue)
= f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone (none):" }, {class: 'select2 select2-compact'})
-
= hidden_field_tag :issue_context
= f.submit class: 'btn'
- elsif issue.milestone
= link_to issue.milestone.title, project_milestone_path
+ - else
+ None
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 87d30a4a163..3b5e398c327 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,92 +1,72 @@
-.ui-box
- .title
+.append-bottom-10
+ .check-all-holder
= check_box_tag "check_all_issues", nil, false, class: "check_all_issues left"
- .clearfix
- .issues_bulk_update.hide
- = form_tag bulk_update_project_issues_path(@project), method: :post do
- %span Update selected issues with &nbsp;
- = select_tag('update[status]', options_for_select(['open', 'closed']), prompt: "Status")
- = select_tag('update[assignee_id]', bulk_update_assignee_options, prompt: "Assignee")
- = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
- = hidden_field_tag 'update[issues_ids]', []
- = hidden_field_tag :status, params[:status]
- = button_tag "Save", class: "btn update_selected_issues btn-small btn-save"
- .issues-filters
- %span Filter by
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-tags
- %span.light labels:
- - if params[:label_name].present?
- %strong= params[:label_name]
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(label_name: nil) do
- Any
- - issue_label_names.each do |label_name|
- %li
- = link_to project_filter_path(label_name: label_name) do
- %span{class: "label #{label_css_class(label_name)}"}
- %i.icon-tag
- = label_name
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-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
- - @project.team.members.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
+ .issues-filters
+ .dropdown.inline
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.icon-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.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-time
- %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
+ .dropdown.inline.prepend-left-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.icon-time
+ %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'
+ .pull-right
+ = render 'shared/sort_dropdown'
+ .clearfix
+ .issues_bulk_update.hide
+ = form_tag bulk_update_project_issues_path(@project), method: :post do
+ = select_tag('update[status]', options_for_select(['Open', 'Closed']), prompt: "Status")
+ = project_users_select_tag('update[assignee_id]', placeholder: 'Assignee')
+ = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
+ = hidden_field_tag 'update[issues_ids]', []
+ = hidden_field_tag :status, params[:status]
+ = button_tag "Update issues", class: "btn update_selected_issues btn-save"
+.ui-box
%ul.well-list.issues-list
= render @issues
- if @issues.blank?
%li
- %h4.nothing_here_message No issues to show
+ .nothing-here-block No issues to show
- if @issues.present?
.pull-right
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 71a89af61a2..5e899d412c6 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,6 +1,6 @@
= render "head"
.row
.col-md-3
- = render 'shared/project_filter', project_entities_path: project_issues_path(@project)
+ = render 'shared/project_filter', project_entities_path: project_issues_path(@project), labels: true
.col-md-9.issues-holder
= render "issues"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index cd4a158e427..124eb53571d 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,26 +1,18 @@
%h3.page-title
Issue ##{@issue.iid}
- %small
- created #{time_ago_with_tooltip(@issue.created_at)}
-
- - if @issue.closed?
- %span.state-label.state-label-red Closed
- - else
- %span.state-label.state-label-green Open
-
%span.pull-right
- if can?(current_user, :write_issue, @project)
- = link_to new_project_issue_path(@project), class: "btn grouped", title: "New Issue", id: "new_issue_link" do
+ = link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do
%i.icon-plus
New Issue
- if can?(current_user, :modify_issue, @issue)
- if @issue.closed?
- = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped reopen_issue"
+ = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen"
- else
- = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue"
+ = 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 grouped" do
+ = link_to edit_project_issue_path(@project, @issue), class: "btn btn-grouped" do
%i.icon-edit
Edit
@@ -37,26 +29,36 @@
= link_to project_milestone_path(@project, @issue.milestone) do
= @issue.milestone.title
-.issue-box
+.issue-box{ class: issue_box_class(@issue) }
+ .state
+ %span.state-label
+ - if @issue.closed?
+ Closed
+ - else
+ Open
+
+ %span.creator
+ Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)}
+
%h4.title
= gfm escape_once(@issue.title)
- .context
- %cite.cgray
- = render partial: 'issue_context', locals: { issue: @issue }
-
- if @issue.description.present?
.description
.wiki
= preserve do
= markdown @issue.description
+ .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 grouped reopen_issue"
+ = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen"
- else
- = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue"
+ = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue"
.participants
%cite.cgray #{@issue.participants.count} participants
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index 59524e0f224..5199e9fc61f 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -3,10 +3,7 @@
:plain
$("##{dom_id(@issue)}").fadeOut();
- elsif params[:issue_context]
- $('.issue-box .context').html("#{escape_javascript(render partial: 'issue_context', locals: { issue: @issue })}");
$('.issue-box .context').effect('highlight');
- $('.select2').select2();
- $('.edit-issue.inline-update input[type="submit"]').hide();
- 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/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index a058d09a1ce..329cf9ceba8 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -7,4 +7,4 @@
- else
.light-well
- %h3.nothing_here_message Add first label to your issues or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels
+ .nothing-here-block Add first label to your issues or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index b4ba127da25..22502760e50 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,32 +1,44 @@
= form_for [@project, @merge_request], html: { class: "merge-request-form form-horizontal" } do |f|
- -if @merge_request.errors.any?
- .alert.alert-danger
- %ul
- - @merge_request.errors.full_messages.each do |msg|
- %li= msg
+ .row
+ .col-sm-2
+ .col-sm-10
+ - if @repository.contribution_guide && !@merge_request.persisted?
+ - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
+ .alert.alert-info
+ Please review the
+ %strong #{link_to "guidelines for contribution", contribution_guide_url}
+ to this repository.
+
+ -if @merge_request.errors.any?
+ .alert.alert-danger
+ - @merge_request.errors.full_messages.each do |msg|
+ %div= msg
.merge-request-branches
- .row
- .col-md-5
+ .form-group
+ = label_tag nil, class: 'control-label' do
+ From
+ .col-sm-10
.clearfix
.pull-left
= f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted? })
.pull-left
&nbsp;
= f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2'})
- .mr_source_commit.prepend-top-10
- .col-md-2
- .merge-request-angle
- %i.icon-long-arrow-right
- .col-md-5
+ .mr_source_commit
+ %br
+ .form-group
+ = label_tag nil, class: 'control-label' do
+ To
+ .col-sm-10
.clearfix
.pull-left
- projects = @project.forked_from_project.nil? ? [@project] : [ @project,@project.forked_from_project]
- = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace'), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? })
+ = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? })
.pull-left
&nbsp;
= f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2'})
- .mr_target_commit.prepend-top-10
+ .mr_target_commit
%hr
.merge-request-form-info
@@ -40,24 +52,6 @@
= f.text_area :description, class: "form-control js-gfm-input", rows: 14
%p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
- %hr
- .form-group
- .merge-request-assignee
- = f.label :assignee_id, class: 'control-label' do
- %i.icon-user
- Assign to
- .col-sm-10
- = f.select(:assignee_id, assignee_options(@merge_request), { include_blank: "Select a user" }, {class: 'select2'})
- &nbsp;
- = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
- .form-group
- .merge-request-milestone
- = f.label :milestone_id, class: 'control-label' do
- %i.icon-time
- Milestone
- .col-sm-10= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
-
-
.form-actions
- if @merge_request.new_record?
= f.submit 'Submit merge request', class: "btn btn-create"
@@ -89,7 +83,3 @@
target_branch.on("change", function() {
$.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
});
- $('.assign-to-me-link').on('click', function(e){
- $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
- e.preventDefault();
- });
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index ff763bca307..980ac126742 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -10,14 +10,14 @@
%span.pull-right
- if merge_request.for_fork?
%span.light
- = "#{merge_request.source_project_path}"
- = "#{merge_request.source_branch}"
+ #{merge_request.source_project_namespace}:
+ = merge_request.source_branch
%i.icon-angle-right.light
- = "#{merge_request.target_branch}"
+ = merge_request.target_branch
- else
- = "#{merge_request.source_branch}"
+ = merge_request.source_branch
%i.icon-angle-right.light
- = "#{merge_request.target_branch}"
+ = merge_request.target_branch
.merge-request-info
- if merge_request.author
authored by #{link_to_member(merge_request.source_project, merge_request.author)}
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 42641765c5c..7a21c0dd069 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -2,7 +2,7 @@
= render "projects/merge_requests/show/mr_title"
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/show/mr_box"
- - if @merge_request.opened?
+ - if @merge_request.open?
- if @merge_request.source_branch_exists? && @merge_request.target_branch_exists?
= render "projects/merge_requests/show/mr_accept"
- else
@@ -12,7 +12,7 @@
= render "projects/merge_requests/show/commits"
- if @commits.present?
- %ul.nav.nav-tabs.append-bottom-10
+ %ul.nav.nav-tabs
%li.notes-tab{data: {action: 'notes'}}
= link_to project_merge_request_path(@project, @merge_request) do
%i.icon-comment
@@ -20,7 +20,14 @@
%li.diffs-tab{data: {action: 'diffs'}}
= link_to diffs_project_merge_request_path(@project, @merge_request) do
%i.icon-list-alt
- Diff
+ Changes
+
+ - content_for :note_actions do
+ - if can?(current_user, :modify_merge_request, @merge_request)
+ - unless @merge_request.closed? || @merge_request.merged?
+ = 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", 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", title: "Close merge request"
.notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml
index ec4d7f2121b..1b1082baafe 100644
--- a/app/views/projects/merge_requests/branch_from.js.haml
+++ b/app/views/projects/merge_requests/branch_from.js.haml
@@ -1,7 +1,7 @@
:plain
- $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project)}");
+ $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}");
var mrTitle = $('#merge_request_title');
if(mrTitle.val().length == 0) {
- mrTitle.val("#{params[:ref].titleize}");
+ mrTitle.val("#{params[:ref].humanize}");
}
diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml
index f4e2886ee44..f7ede0ded53 100644
--- a/app/views/projects/merge_requests/branch_to.js.haml
+++ b/app/views/projects/merge_requests/branch_to.js.haml
@@ -1,2 +1,2 @@
:plain
- $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project)}");
+ $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}");
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index a525a49015f..34faebf619c 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -10,64 +10,62 @@
.col-md-3
= render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project)
.col-md-9
- .ui-box
- .title
- .mr-filters
- %span Filter by
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-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
- - @project.team.members.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
+ .mr-filters.append-bottom-10
+ .dropdown.inline
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.icon-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.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-time
- %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
+ .dropdown.inline.prepend-left-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.icon-time
+ %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'
+ .pull-right
+ = render 'shared/sort_dropdown'
+ .ui-box
%ul.well-list.mr-list
= render @merge_requests
- if @merge_requests.blank?
%li
- %h4.nothing_here_message No merge requests to show
+ .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
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index 8ca1326c96a..9b4271bbffc 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -29,7 +29,7 @@
= render "projects/commits/commit", commit: commit, project: @merge_request.source_project
- else
- %h4.nothing_here_message
+ .nothing-here-block
Nothing to merge from
%span.label-branch #{@merge_request.source_branch}
to
diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml
new file mode 100644
index 00000000000..2bd850426a9
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_context.html.haml
@@ -0,0 +1,22 @@
+= form_for [@project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f|
+ %strong.append-right-10
+ 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', selected: @merge_request.assignee_id)
+ - elsif merge_request.assignee
+ = link_to_member(@project, @merge_request.assignee)
+ - else
+ None
+
+ .pull-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 (none):" }, {class: 'select2 select2-compact'})
+ = 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/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index 2917accdb5d..3d48514f98b 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -1,11 +1,11 @@
- if @merge_request_diff.collected?
= render "projects/commits/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project
- elsif @merge_request_diff.empty?
- %h4.nothing_here_message Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
+ .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
- else
.bs-callout.bs-callout-warning
%h4
- Diff for this comparison is extremely large.
+ Changes view for this comparison is extremely large.
%p
You can
= link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request, format: :diff), class: "vlink"
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 a9cbe43fb16..bd7c8435f4c 100644
--- a/app/views/projects/merge_requests/show/_mr_accept.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -1,6 +1,13 @@
- unless @allowed_to_merge
- .bs-callout
- %strong You don't have permission to merge this MR
+ - if @project.archived?
+ .bs-callout.bs-callout-warning
+ %strong Archived projects cannot be committed to!
+ - else
+ .bs-callout
+ .automerge_widget.cannot_be_merged.hide
+ %strong This can't be merged automatically, even if it could be merged you don't have the permission to do so.
+ .automerge_widget.can_be_merged.hide
+ %strong This can be merged automatically but you don't have the permission to do so.
- if @show_merge_controls
@@ -15,18 +22,19 @@
= link_to "click here", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
for instructions.
- %br
- If you want to modify merge commit message -
- %strong
- = link_to "click here", "#", class: "modify-merge-commit-link js-toggle-visibility-link", title: "Modify merge commit message"
- .js-toggle-visibility-container.hide
- .form-group
- = label_tag :merge_commit_message, "Commit message", class: 'control-label'
- .col-sm-10
- = text_area_tag :merge_commit_message, @merge_request.merge_commit_message, class: "form-control js-gfm-input", rows: 14, required: true
- %p.hint
- The recommended maximum line length is 52 characters for the first line and 72 characters for all following lines.
+ .js-toggle-container
+ %p
+ If you want to modify merge commit message -
+ %strong
+ = link_to "click here", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message"
+ .js-toggle-content.hide
+ .form-group
+ = label_tag :merge_commit_message, "Commit message", class: 'control-label'
+ .col-sm-10
+ = text_area_tag :merge_commit_message, @merge_request.merge_commit_message, class: "form-control js-gfm-input", rows: 14, required: true
+ %p.hint
+ The recommended maximum line length is 52 characters for the first line and 72 characters for all following lines.
.accept-group
.pull-left
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 b4f648ab197..abdcd127a0c 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,18 +1,18 @@
-.issue-box
- %h4.title
- = gfm escape_once(@merge_request.title)
+.issue-box{ class: issue_box_class(@merge_request) }
+ .state
+ %span.state-label
+ - if @merge_request.merged?
+ Merged
+ - elsif @merge_request.closed?
+ Closed
+ - else
+ Open
- .context
- %cite.cgray
- Created by #{link_to_member(@project, @merge_request.author)}.
- - if @merge_request.assignee
- Currently assigned to #{link_to_member(@project, @merge_request.assignee)}.
- - if @merge_request.milestone
- .pull-right
- - milestone = @merge_request.milestone
- %cite.cgray Attached to milestone
- %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
+ %span.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)
- if @merge_request.description.present?
.description
@@ -20,22 +20,26 @@
= preserve do
= markdown @merge_request.description
- - if @merge_request.closed?
- .description.alert-danger
- %span
- %i.icon-remove
- Closed by #{link_to_member(@project, @merge_request.closed_event.author)}
- #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}.
- - if @merge_request.merged?
- .description.alert-success
- %span
- %i.icon-ok
- Merged by #{link_to_member(@project, @merge_request.merge_event.author)}
- #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}.
- - if !@closes_issues.empty? && @merge_request.opened?
- .description.alert-info
- %span
- %i.icon-ok
- Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
- = succeed '.' do
- != gfm(@closes_issues.map { |i| "##{i.iid}" }.to_sentence)
+ .context
+ %cite.cgray
+ = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request }
+
+- if @merge_request.closed?
+ .alert.alert-info
+ %span
+ %i.icon-remove
+ Closed by #{link_to_member(@project, @merge_request.closed_event.author)}
+ #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
+- if @merge_request.merged?
+ .alert.alert-info
+ %span
+ %i.icon-ok
+ Merged by #{link_to_member(@project, @merge_request.merge_event.author)}
+ #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
+- if !@closes_issues.empty? && @merge_request.open?
+ .alert.alert-info.alert-info
+ %span
+ %i.icon-ok
+ Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
+ = succeed '.' do
+ != gfm(@closes_issues.map { |i| "##{i.iid}" }.to_sentence)
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 08a3fdf869a..7676fc137c7 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,26 +1,11 @@
%h3.page-title
= "Merge Request ##{@merge_request.iid}"
- %small
- created #{time_ago_with_tooltip(@merge_request.created_at)}
-
- - if @merge_request.merged?
- %span.state-label.state-label-green
- %i.icon-ok
- Merged
- - elsif @merge_request.closed?
- %span.state-label.state-label-red
- Closed
- - else
- %span.state-label.state-label-green
- Open
-
-
%span.pull-right
- if can?(current_user, :modify_merge_request, @merge_request)
- - if @merge_request.opened?
- .left.btn-group
- %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} }
+ - if @merge_request.open?
+ .btn-group.pull-left
+ %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
%i.icon-download-alt
Download as
%span.caret
@@ -28,13 +13,15 @@
%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 grouped btn-close", title: "Close merge request"
+ = 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 grouped", id:"edit_merge_request" do
+ = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn btn-grouped", id:"edit_merge_request" do
%i.icon-edit
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
+.votes-holder.hidden-sm.hidden-xs
#votes= render 'votes/votes_block', votable: @merge_request
.back-link
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
new file mode 100644
index 00000000000..6452cc6382d
--- /dev/null
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -0,0 +1,2 @@
+- if params[:merge_request_context]
+ $('.issue-box .context').effect('highlight');
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 4dec3838d97..5579659d60e 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,7 +1,7 @@
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
.pull-right
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link grouped" do
+ = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link btn-grouped" do
%i.icon-edit
Edit
= link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-small btn-remove"
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 6cfe4d28778..3537650ad43 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -26,6 +26,6 @@
- if @milestones.blank?
%li
- %h3.nothing_here_message No milestones to show
+ .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 4d504380c93..06cf9946784 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -3,21 +3,15 @@
Milestone ##{@milestone.iid}
%small
= @milestone.expires_at
- - if @milestone.closed?
- %span.state-label.state-label-red Closed
- - elsif @milestone.expired?
- %span.state-label.state-label-red Expired
- - else
- %span.state-label.state-label-green Open
.pull-right
- if can?(current_user, :admin_milestone, @project)
- = link_to edit_project_milestone_path(@project, @milestone), class: "btn grouped" do
+ = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do
%i.icon-edit
Edit
- if @milestone.active?
- = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-remove grouped"
+ = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-remove btn-grouped"
- else
- = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn grouped"
+ = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped"
- if @milestone.issues.any? && @milestone.can_be_closed?
.alert.alert-success
@@ -28,7 +22,16 @@
&larr; To milestones list
-.issue-box
+.issue-box{ class: issue_box_class(@milestone) }
+ .state
+ %span.state-label
+ - if @milestone.closed?
+ Closed
+ - elsif @milestone.expired?
+ Expired
+ - else
+ Open
+
%h4.title
= gfm escape_once(@milestone.title)
@@ -48,7 +51,7 @@
= preserve do
= markdown @milestone.description
-%ul.nav.nav-tabs.append-bottom-10
+%ul.nav.nav-tabs
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
@@ -63,10 +66,10 @@
%span.badge= @users.count
.pull-right
- = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small grouped", title: "New Issue" do
+ = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small btn-grouped", title: "New Issue" do
%i.icon-plus
New Issue
- = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small edit-milestone-link grouped"
+ = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small 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 9ee54fef062..5d5637c1588 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -9,18 +9,6 @@
%strong Project name
.col-sm-10
= f.text_field :name, placeholder: "Example Project", class: "form-control", tabindex: 1, autofocus: true
- .help-inline
- = link_to "#", class: 'js-toggle-visibility-link' do
- %span Customize repository name?
-
- .form-group.js-toggle-visibility-container.hide
- = 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
-
- if current_user.can_select_namespace?
.form-group
@@ -29,19 +17,42 @@
.col-sm-10
= f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2}
- .form-group
- .col-sm-2
- .col-sm-10
- = link_to "#", class: 'appear-link' do
- %i.icon-upload-alt
- %span Import existing repository?
- .form-group.appear-data.import-url-data
- = f.label :import_url, class: 'control-label' do
- %span Import existing repo
- .col-sm-10
- = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
- .light
- URL must be cloneable
+ %hr
+ .js-toggle-container
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = link_to "#", class: 'js-toggle-button' do
+ %i.icon-edit
+ %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.icon-upload-alt
+ %span Import existing repository?
+ .js-toggle-content.hide
+ .form-group.import-url-data
+ = f.label :import_url, class: 'control-label' do
+ %span Import existing repo
+ .col-sm-10
+ = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
+ .bs-callout.bs-callout-info
+ This url must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
+ %br
+ The import will time out after 2 minutes. For big repositories, use a clone/push combination.
+ %hr
+
.form-group
= f.label :description, class: 'control-label' do
Description
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index 9537ab18caa..9acadc6a14e 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -1,7 +1,7 @@
- note = notes.first # example note
-# Check if line want not changed since comment was left
- if !defined?(line) || line == note.diff_line
- %tr.notes_holder
+ %tr.notes_holder.js-toggle-content
%td.notes_line{ colspan: 2 }
%span.btn.disabled
%i.icon-comment
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index 936dbb354cd..2012aa021b9 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -1,6 +1,6 @@
- note1 = notes1.first # example note
- note2 = notes2.first # example note
-%tr.notes_holder
+%tr.notes_holder.js-toggle-content
-# Check if line want not changed since comment was left
/- if !defined?(line1) || line1 == note1.diff_line
- if note1
diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml
index ee65ae1e2f5..9b1f4d77587 100644
--- a/app/views/projects/notes/_discussion.html.haml
+++ b/app/views/projects/notes/_discussion.html.haml
@@ -36,21 +36,17 @@
.discussion-body
- if note.for_diff_line?
- if note.active?
- .content
- .file= render "projects/notes/discussion_diff", discussion_notes: discussion_notes, note: note
+ = render "projects/notes/discussion_diff", discussion_notes: discussion_notes, note: note
- else
= link_to 'show outdated discussion', '#', class: 'js-show-outdated-discussion'
%div.hide.outdated-discussion
- .content
- .notes{ rel: discussion_notes.first.discussion_id }
- = render discussion_notes
-
+ .notes{ rel: discussion_notes.first.discussion_id }
+ = render discussion_notes
- else
- .content
- .notes{ rel: discussion_notes.first.discussion_id }
- = render discussion_notes
- = render "projects/notes/discussion_reply_button", note: discussion_notes.first
+ .notes{ rel: discussion_notes.first.discussion_id }
+ = render discussion_notes
+ = render "projects/notes/discussion_reply_button", note: discussion_notes.first
-# will be shown when the other one is hidden
.discussion-hidden.content.hide
@@ -59,4 +55,3 @@
= link_to "javascript:;", class: "js-details-target js-toggler-target" do
%i.icon-eye-open
Show
-
diff --git a/app/views/projects/notes/_discussion_diff.html.haml b/app/views/projects/notes/_discussion_diff.html.haml
index c3f41a1b6b5..687fac04973 100644
--- a/app/views/projects/notes/_discussion_diff.html.haml
+++ b/app/views/projects/notes/_discussion_diff.html.haml
@@ -1,24 +1,25 @@
- diff = note.diff
-.header
- - if diff.deleted_file
- %span= diff.old_path
- - else
- %span= diff.new_path
- - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
- %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
- %br/
-.content
- %table
- - each_diff_line_near(diff, note.diff_file_index, note.line_code) do |line, type, line_code, line_new, line_old|
- %tr.line_holder{ id: line_code }
- - if type == "match"
- %td.old_line= "..."
- %td.new_line= "..."
- %td.line_content.matched= line
- - else
- %td.old_line= raw(type == "new" ? "&nbsp;" : line_old)
- %td.new_line= raw(type == "old" ? "&nbsp;" : line_new)
- %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} &nbsp;"
+.diff-file
+ .diff-header
+ - if diff.deleted_file
+ %span= diff.old_path
+ - else
+ %span= diff.new_path
+ - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
+ %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
+ %br/
+ .diff-content
+ %table
+ - each_diff_line_near(diff, note.diff_file_index, note.line_code) do |line, type, line_code, line_new, line_old|
+ %tr.line_holder{ id: line_code }
+ - if type == "match"
+ %td.old_line= "..."
+ %td.new_line= "..."
+ %td.line_content.matched= line
+ - else
+ %td.old_line= raw(type == "new" ? "&nbsp;" : line_old)
+ %td.new_line= raw(type == "old" ? "&nbsp;" : line_new)
+ %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} &nbsp;"
- - if line_code == note.line_code
- = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
+ - if line_code == note.line_code
+ = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index bcaedc8bd7c..3db551e993b 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -22,7 +22,7 @@
.note-form-actions
.buttons
- = f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button"
+ = f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button"
= yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 217e36e38d0..81bf0611ec6 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -23,7 +23,7 @@
%i.icon-thumbs-up
\+1
- if note.downvote?
- %span.vote.downvote.label.label-error
+ %span.vote.downvote.label.label-danger
%i.icon-thumbs-down
\-1
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index a32679a18db..8b100766e97 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -9,7 +9,7 @@
%ul
%li keep stable branches secured
%li forced code review before merge to protected branches
- %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined_link"}
+ %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined-link"}
- if can? current_user, :admin_project, @project
= form_for [@project, @protected_branch], html: { class: 'form-horizontal' } do |f|
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
index b03feded0a7..88c1cfa28e0 100644
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ b/app/views/projects/repositories/_download_archive.html.haml
@@ -3,7 +3,7 @@
- split_button = split_button || false
- if split_button == true
%span.btn-group{class: btn_class}
- = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn' do
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
%i.icon-download-alt
%span Download zip
%a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
@@ -12,26 +12,26 @@
Select Archive Format
%ul.dropdown-menu{ role: 'menu' }
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'zip') do
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.icon-download-alt
%span Download zip
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz') do
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.icon-download-alt
%span Download tar.gz
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2') do
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
%i.icon-download-alt
%span Download tar.bz2
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar') do
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'tar'), rel: 'nofollow' do
%i.icon-download-alt
%span Download tar
- else
%span.btn-group{class: btn_class}
- = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn' do
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
%i.icon-download-alt
%span zip
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn' do
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
%i.icon-download-alt
%span tar.gz \ No newline at end of file
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 32a42916bd6..20879d69091 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -9,8 +9,8 @@
.col-md-3.project-side.hidden-sm
.clearfix
- if @project.archived?
- .alert
- %h5
+ .alert.alert-warning
+ %h4
%i.icon-warning-sign
Archived project!
%p Repository is read-only
@@ -51,7 +51,7 @@
%p
%span.light Owned by
- if @project.group
- #{link_to @project.group.name, @project.group} Group
+ #{link_to @project.group.name, @project.group} group
- else
#{link_to @project.owner_name, @project.owner}
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index c40f63d05b3..e60f9a44322 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -12,4 +12,4 @@
= render partial: "projects/snippets/snippet", collection: @snippets
- if @snippets.empty?
%li
- %h3.nothing_here_message Nothing here.
+ .nothing-here-block Nothing here.
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 70dedcf9155..6629e47ad38 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -15,7 +15,7 @@
%span.pull-right
- if can? current_user, :download_code, @project
- = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'grouped btn-group-small'
+ = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small'
- if can?(current_user, :admin_project, @project)
= link_to project_tag_path(@project, tag.name), class: 'btn btn-small remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do
%i.icon-trash
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 2d53a5dd66a..53f3e67ff2c 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -18,7 +18,7 @@
= paginate @tags, theme: 'gitlab'
- else
- %h3.nothing_here_message
+ .nothing-here-block
Repository has no tags yet.
%br
%small
diff --git a/app/views/projects/team_members/_group_members.html.haml b/app/views/projects/team_members/_group_members.html.haml
index 68f08006854..eceec6627b9 100644
--- a/app/views/projects/team_members/_group_members.html.haml
+++ b/app/views/projects/team_members/_group_members.html.haml
@@ -1,10 +1,14 @@
+- group_users_count = @group.users_groups.count
.ui-box
.title
%strong #{@group.name}
- group members (#{@group.users_groups.count})
+ group members (#{group_users_count})
.pull-right
= link_to members_group_path(@group), class: 'btn btn-small' do
%i.icon-edit
%ul.well-list
- - @group.users_groups.order('group_access DESC').each do |member|
+ - @group.users_groups.order('group_access DESC').limit(20).each do |member|
= render 'users_groups/users_group', member: member, show_controls: false
+ - if group_users_count > 20
+ %li
+ and #{group_users_count - 20} more. For full list visit #{link_to 'group members page', members_group_path(@group)}
diff --git a/app/views/projects/team_members/index.html.haml b/app/views/projects/team_members/index.html.haml
index acbe82919f1..6eccaafe3de 100644
--- a/app/views/projects/team_members/index.html.haml
+++ b/app/views/projects/team_members/index.html.haml
@@ -3,9 +3,9 @@
- if can? current_user, :admin_team_member, @project
%span.pull-right
- = link_to new_project_team_member_path(@project), class: "btn btn-new grouped", title: "New project member" do
+ = link_to new_project_team_member_path(@project), class: "btn btn-new btn-grouped", title: "New project member" do
New project member
- = link_to import_project_team_members_path(@project), class: "btn grouped", title: "Import members from another project" do
+ = link_to import_project_team_members_path(@project), class: "btn btn-grouped", title: "Import members from another project" do
Import members
%p.light
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index ab572f2e97b..0c19109445a 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,7 +1,7 @@
.readme-holder#README
- %h4
+ %h4.readme-file-title
%i.icon-file
- = readme.name
+ = readme.name
.wiki
- if gitlab_markdown?(readme.name)
= preserve do
diff --git a/app/views/projects/walls/show.html.haml b/app/views/projects/walls/show.html.haml
index c6afec443f4..3e3c7c4f8dd 100644
--- a/app/views/projects/walls/show.html.haml
+++ b/app/views/projects/walls/show.html.haml
@@ -9,7 +9,7 @@
= f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on'
.note-form-actions
.buttons
- = f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button"
+ = f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button"
.note-form-option
%a.choose-btn.btn.btn-small.js-choose-note-attachment-button
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 1001bb10c4f..5dd769dcfe1 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,8 +1,8 @@
%span.pull-right
- if (@wiki && @wiki.persisted?)
- = link_to history_project_wiki_path(@project, @wiki), class: "btn grouped" do
+ = link_to history_project_wiki_path(@project, @wiki), class: "btn btn-grouped" do
Page History
- if can?(current_user, :write_wiki, @project)
- = link_to edit_project_wiki_path(@project, @wiki), class: "btn grouped" do
+ = link_to edit_project_wiki_path(@project, @wiki), class: "btn btn-grouped" do
%i.icon-edit
Edit
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index 5e5aa5170d6..0a7e51e974c 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,4 +1,4 @@
-%ul.nav.nav-tabs.append-bottom-20
+%ul.nav.nav-tabs
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
= link_to 'Home', project_wiki_path(@project, :home)
diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml
index 2fc93c5b742..624ec0b9b95 100644
--- a/app/views/public/projects/index.html.haml
+++ b/app/views/public/projects/index.html.haml
@@ -63,6 +63,6 @@
%i.icon-warning-sign
Empty repository
- unless @projects.present?
- %h3.nothing_here_message No public projects
+ .nothing-here-block No public projects
= paginate @projects, theme: "gitlab"
diff --git a/app/views/search/_project_results.html.haml b/app/views/search/_project_results.html.haml
index ea324b3a9aa..f285bda5736 100644
--- a/app/views/search/_project_results.html.haml
+++ b/app/views/search/_project_results.html.haml
@@ -1,4 +1,4 @@
-%ul.nav.nav-tabs.append-bottom-10
+%ul.nav.nav-tabs
%li{class: ("active" if params[:search_code].present?)}
= link_to search_path(params.merge(search_code: true)) do
Repository Code
diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml
index 6063b4a0732..0becf531cc3 100644
--- a/app/views/shared/_filter.html.haml
+++ b/app/views/shared/_filter.html.haml
@@ -1,16 +1,17 @@
.side-filters.hidden-xs.hidden-sm
= form_tag filter_path(entity), method: 'get' do
- %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
- %li{class: ("active" if params[:scope] == 'authored')}
- = link_to filter_path(entity, scope: 'authored') do
- Created by me
- %li{class: ("active" if params[:scope] == 'all')}
- = link_to filter_path(entity, scope: 'all') do
- Everyone's
+ - 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
+ %li{class: ("active" if params[:scope] == 'authored')}
+ = link_to filter_path(entity, scope: 'authored') do
+ Created by me
+ %li{class: ("active" if params[:scope] == 'all')}
+ = link_to filter_path(entity, scope: 'all') do
+ Everyone's
%fieldset.status-filter
%legend State
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 199000656fe..087b6632e8d 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -11,5 +11,5 @@
= render 'projects/issues/issue', issue: issue
= paginate @issues, theme: "gitlab"
- else
- %p.nothing_here_message No issues to show
+ .nothing-here-block No issues to show
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index ddad28339c8..f40b7be4864 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -11,4 +11,4 @@
= paginate @merge_requests, theme: "gitlab"
- else
- %h3.nothing_here_message No merge requests to show
+ .nothing-here-block No merge requests to show
diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml
index 9b89c5c8007..d82b08eeaa2 100644
--- a/app/views/shared/_project_filter.html.haml
+++ b/app/views/shared/_project_filter.html.haml
@@ -26,6 +26,26 @@
= link_to project_filter_path(state: 'all') do
All
+ - if defined?(labels)
+ %fieldset
+ %legend Labels
+ %ul.nav.nav-pills.nav-stacked.nav-small.labels-filter
+ - issue_label_names.each do |label_name|
+ %li{class: label_filter_class(label_name)}
+ = link_to labels_filter_path(label_name) do
+ %span{class: "label #{label_css_class(label_name)}"}
+ %i.icon-tag
+ = label_name
+ - if selected_label?(label_name)
+ .pull-right
+ %i.icon-remove
+
+ - if issue_label_names.empty?
+ .light-well
+ Add first label to your issues
+ %br
+ or #{link_to 'generate', generate_project_labels_path(@project), 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
diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml
index c97f8ba0f0e..7dec48e6585 100644
--- a/app/views/shared/_promo.html.haml
+++ b/app/views/shared/_promo.html.haml
@@ -1,4 +1,4 @@
.gitlab-promo
- = link_to "Homepage", "http://gitlab.org"
- = link_to "Blog", "http://blog.gitlab.org"
+ = link_to "Homepage", "https://www.gitlab.com/"
+ = link_to "Blog", "https://www.gitlab.com/blog/"
= link_to "@gitlabhq", "https://twitter.com/gitlabhq"
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index 2e875669967..7b37b39780e 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -1,5 +1,5 @@
.dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light sort:
- if @sort.present?
= @sort
diff --git a/app/views/snippets/_blob_content.html.haml b/app/views/snippets/_blob_content.html.haml
index 81055451b66..8cec6168ab8 100644
--- a/app/views/snippets/_blob_content.html.haml
+++ b/app/views/snippets/_blob_content.html.haml
@@ -11,4 +11,4 @@
= render 'shared/file_hljs', blob: @snippet
- else
.file-content.code
- %p.nothing_here_message Empty file
+ .nothing-here-block Empty file
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
index 05365dd8b88..40df42b6cf5 100644
--- a/app/views/snippets/_snippets.html.haml
+++ b/app/views/snippets/_snippets.html.haml
@@ -2,6 +2,6 @@
= render partial: 'snippet', collection: @snippets
- if @snippets.empty?
%li
- %h3.nothing_here_message Nothing here.
+ .nothing-here-block Nothing here.
= paginate @snippets, theme: 'gitlab'
diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml
index bf712b2c7e7..90ddc7198f6 100644
--- a/app/views/snippets/current_user_index.html.haml
+++ b/app/views/snippets/current_user_index.html.haml
@@ -1,9 +1,9 @@
%h3.page-title
My Snippets
.pull-right
- = link_to new_snippet_path, class: "btn btn-new grouped", title: "New Snippet" do
+ = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
Add new snippet
- = link_to snippets_path, class: "btn grouped" do
+ = link_to snippets_path, class: "btn btn-grouped" do
Discover snippets
%p.light
diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml
index 2f6c914a159..cea2517a8e1 100644
--- a/app/views/snippets/index.html.haml
+++ b/app/views/snippets/index.html.haml
@@ -2,9 +2,9 @@
Public snippets
.pull-right
- = link_to new_snippet_path, class: "btn btn-new grouped", title: "New Snippet" do
+ = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
Add new snippet
- = link_to user_snippets_path(current_user), class: "btn grouped" do
+ = link_to user_snippets_path(current_user), class: "btn btn-grouped" do
My snippets
%p.light
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 98210af1e3d..edcaf3acf98 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -14,10 +14,10 @@
%small member since #{@user.created_at.stamp("Nov 12, 2031")}
.clearfix
%h4 Groups:
- = render 'groups', groups: @user.groups
+ = render 'groups', groups: @groups
%hr
%h4 User Activity:
= render @events
.col-md-4
= render 'profile', user: @user
- = render 'projects', user: @user
+ = render 'projects'
diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml
index b66b486fbc5..ad363eaba23 100644
--- a/app/views/users_groups/_users_group.html.haml
+++ b/app/views/users_groups/_users_group.html.haml
@@ -1,28 +1,31 @@
- user = member.user
- return unless user
+- show_roles = true if show_roles.nil?
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
- = image_tag avatar_icon(user.email, 16), class: "avatar s16"
- %strong= user.name
- %span.cgray= user.username
- - if user == current_user
- %span.label.label-success It's you
+ %span{class: ("list-item-name" if show_controls)}
+ = image_tag avatar_icon(user.email, 16), class: "avatar s16"
+ %strong= user.name
+ %span.cgray= user.username
+ - if user == current_user
+ %span.label.label-success It's you
- %span.pull-right
- %strong= member.human_access
- - if show_controls
- - if can?(current_user, :modify, member)
- = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do
- %i.icon-edit
- - if can?(current_user, :destroy, member)
- - if current_user == member.user
- = link_to leave_profile_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
- %i.icon-minus.icon-white
- - else
- = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
- %i.icon-minus.icon-white
+ - if show_roles
+ %span.pull-right
+ %strong= member.human_access
+ - if show_controls
+ - if can?(current_user, :modify, member)
+ = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do
+ %i.icon-edit
+ - if can?(current_user, :destroy, member)
+ - if current_user == member.user
+ = link_to leave_profile_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ %i.icon-minus.icon-white
+ - else
+ = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ %i.icon-minus.icon-white
- .edit-member.hide.js-toggle-content
- = form_for [@group, member], remote: true do |f|
- .alert.prepend-top-20
- = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access)
- = f.submit 'Save', class: 'btn btn-save btn-small'
+ .edit-member.hide.js-toggle-content
+ = form_for [@group, member], remote: true do |f|
+ .alert.prepend-top-20
+ = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access)
+ = f.submit 'Save', class: 'btn btn-save btn-small'
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 6416aa608ec..3b0cf77d42e 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -29,10 +29,20 @@ class PostReceive
return false
end
- GitPushService.new.execute(project, user, oldrev, newrev, ref)
+ if tag?(ref)
+ GitTagPushService.new.execute(project, user, oldrev, newrev, ref)
+ else
+ GitPushService.new.execute(project, user, oldrev, newrev, ref)
+ end
end
def log(message)
Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
end
+
+ private
+
+ def tag?(ref)
+ !!(/refs\/tags\/(.*)/.match(ref))
+ end
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 95b80bca7c0..e66df8c4db1 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -11,11 +11,11 @@ class RepositoryImportWorker
project.import_url)
if result
- project.imported = true
+ project.import_finish
project.save
project.satellite.create unless project.satellite.exists?
else
- project.imported = false
+ project.import_fail
end
end
end
diff --git a/bin/bundle b/bin/bundle
new file mode 100755
index 00000000000..66e9889e8b4
--- /dev/null
+++ b/bin/bundle
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+load Gem.bin_path('bundler', 'bundle')
diff --git a/bin/rails b/bin/rails
new file mode 100755
index 00000000000..7feb6a30e69
--- /dev/null
+++ b/bin/rails
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path("../spring", __FILE__)
+rescue LoadError
+end
+APP_PATH = File.expand_path('../../config/application', __FILE__)
+require_relative '../config/boot'
+require 'rails/commands'
diff --git a/bin/rake b/bin/rake
new file mode 100755
index 00000000000..8017a0271d2
--- /dev/null
+++ b/bin/rake
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path("../spring", __FILE__)
+rescue LoadError
+end
+require_relative '../config/boot'
+require 'rake'
+Rake.application.run
diff --git a/bin/rspec b/bin/rspec
new file mode 100755
index 00000000000..41e37089ac2
--- /dev/null
+++ b/bin/rspec
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path("../spring", __FILE__)
+rescue LoadError
+end
+require 'bundler/setup'
+load Gem.bin_path('rspec', 'rspec')
diff --git a/bin/spinach b/bin/spinach
new file mode 100755
index 00000000000..a080e286cfe
--- /dev/null
+++ b/bin/spinach
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path("../spring", __FILE__)
+rescue LoadError
+end
+require 'bundler/setup'
+load Gem.bin_path('spinach', 'spinach')
diff --git a/bin/spring b/bin/spring
new file mode 100755
index 00000000000..253ec37c345
--- /dev/null
+++ b/bin/spring
@@ -0,0 +1,18 @@
+#!/usr/bin/env ruby
+
+# This file loads spring without using Bundler, in order to be fast
+# It gets overwritten when you run the `spring binstub` command
+
+unless defined?(Spring)
+ require "rubygems"
+ require "bundler"
+
+ if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m)
+ ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR)
+ ENV["GEM_HOME"] = ""
+ Gem.paths = ENV
+
+ gem "spring", match[1]
+ require "spring/binstub"
+ end
+end
diff --git a/config/application.rb b/config/application.rb
index c53257090cf..a782dd1d01e 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -12,14 +12,14 @@ module Gitlab
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
- config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns #{config.root}/app/models/project_services)
+ config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/finders #{config.root}/app/models/concerns #{config.root}/app/models/project_services)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Activate observers that should always be running.
- config.active_record.observers = :activity_observer,
+ config.active_record.observers = :milestone_observer,
:project_activity_cache_observer,
:issue_observer,
:key_observer,
@@ -64,7 +64,7 @@ module Gitlab
config.assets.enabled = true
config.assets.paths << Emoji.images_path
config.assets.precompile << "emoji/*.png"
-
+
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
diff --git a/config/database.yml.mysql b/config/database.yml.mysql
index 55ac088bc1d..6943aaca45b 100644
--- a/config/database.yml.mysql
+++ b/config/database.yml.mysql
@@ -7,6 +7,7 @@ production:
reconnect: false
database: gitlabhq_production
pool: 10
+ reaping_frequency: 10
username: git
password: "secure password"
# host: localhost
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 9ac4622abc2..ad3c03d8fc9 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -33,6 +33,12 @@ Gitlab::Application.configure do
# See everything in the log (default is :info)
# config.log_level = :debug
+ # Suppress 'Rendered template ...' messages in the log
+ # source: http://stackoverflow.com/a/16369363
+ %w{render_template render_partial render_collection}.each do |event|
+ ActiveSupport::Notifications.unsubscribe "#{event}.action_view"
+ end
+
# Prepend all log lines with the following tags
# config.log_tags = [ :subdomain, :uuid ]
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index ce57465d687..9364181eaa4 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -121,7 +121,6 @@ production: &base
ldap:
enabled: false
host: '_your_ldap_server'
- base: '_the_base_where_you_search_for_users'
port: 636
uid: 'sAMAccountName'
method: 'ssl' # "tls" or "ssl" or "plain"
@@ -138,6 +137,20 @@ production: &base
# disable this setting, because the userPrincipalName contains an '@'.
allow_username_or_email_login: true
+ # Base where we can search for users
+ #
+ # Ex. ou=People,dc=gitlab,dc=example
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515
+ # Ex. (employeeType=developer)
+ #
+ user_filter: ''
+
+
## OmniAuth settings
omniauth:
# Allow login via Twitter, Google, etc. using OmniAuth providers
@@ -217,6 +230,10 @@ production: &base
## Google analytics. Uncomment if you want it
# google_analytics_id: '_your_tracking_id'
+ ## Piwik analytics.
+ # piwik_url: '_your_piwik_url'
+ # piwik_site_id: '_your_piwik_site_id'
+
## Text under sign-in page (Markdown enabled)
# sign_in_text: |
# ![Company Logo](http://www.companydomain.com/logo.png)
@@ -227,6 +244,11 @@ development:
test:
<<: *base
+ gravatar:
+ enabled: true
+ gitlab:
+ host: localhost
+ port: 80
issues_tracker:
redmine:
title: "Redmine"
diff --git a/config/initializers/2_app.rb b/config/initializers/2_app.rb
index e2f98002347..655590dff0b 100644
--- a/config/initializers/2_app.rb
+++ b/config/initializers/2_app.rb
@@ -1,6 +1,6 @@
module Gitlab
VERSION = File.read(Rails.root.join("VERSION")).strip
- REVISION = `git log --pretty=format:'%h' -n 1`
+ REVISION = Gitlab::Popen.popen(%W(git log --pretty=format:%h -n 1)).first.chomp
def self.config
Settings
diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb
new file mode 100644
index 00000000000..a7ee3c59822
--- /dev/null
+++ b/config/initializers/6_rack_profiler.rb
@@ -0,0 +1,6 @@
+if Rails.env == 'development'
+ require 'rack-mini-profiler'
+
+ # initialization is skipped so trigger it
+ Rack::MiniProfilerRails.initialize!(Rails.application)
+end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index a02bf9d4aec..50669ece7a8 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -4,7 +4,7 @@ Devise.setup do |config|
# ==> Mailer Configuration
# Configure the e-mail address which will be shown in Devise::Mailer,
# note that it will be overwritten if you use your own mailer class with default "from" parameter.
- config.mailer_sender = Gitlab.config.gitlab.email_from
+ config.mailer_sender = "GitLab <#{Gitlab.config.gitlab.email_from}>"
# Configure the class responsible to send e-mails.
diff --git a/config/initializers/gemoji.rb b/config/initializers/gemoji.rb
deleted file mode 100644
index 6cc33aced77..00000000000
--- a/config/initializers/gemoji.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-# Workaround for https://github.com/github/gemoji/pull/18
-require 'gemoji'
-Gitlab::Application.config.assets.paths << Emoji.images_path
diff --git a/config/routes.rb b/config/routes.rb
index 8c66ad741f9..709b66d3e06 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -124,6 +124,7 @@ Gitlab::Application.routes.draw do
end
end
resources :keys
+ resources :emails, only: [:index, :create, :destroy]
resources :groups, only: [:index] do
member do
delete :leave
@@ -166,7 +167,7 @@ Gitlab::Application.routes.draw do
resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create]
- devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations }
+ devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords}
#
# Project Area
@@ -178,6 +179,8 @@ Gitlab::Application.routes.draw do
post :archive
post :unarchive
get :autocomplete_sources
+ get :import
+ put :retry_import
end
scope module: :projects do
diff --git a/config/unicorn_development.rb b/config/unicorn_development.rb
new file mode 100644
index 00000000000..94a7061451d
--- /dev/null
+++ b/config/unicorn_development.rb
@@ -0,0 +1,2 @@
+worker_processes 2
+timeout 30
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index 3e76d76e838..42d18435340 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -8,8 +8,7 @@ User.seed(:id, [
password_confirmation: "5iveL!fe",
admin: true,
projects_limit: 100,
- theme_id: Gitlab::Theme::MARS
+ theme_id: Gitlab::Theme::MARS,
+ confirmed_at: DateTime.now
}
])
-
-User.find(1).confirm!
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 50726a5a51e..9303ab93300 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -1,51 +1,55 @@
-project_urls = [
- 'https://github.com/documentcloud/underscore.git',
- 'https://github.com/diaspora/diaspora.git',
- 'https://github.com/diaspora/diaspora-project-site.git',
- 'https://github.com/diaspora/diaspora-client.git',
- 'https://github.com/brightbox/brightbox-cli.git',
- 'https://github.com/brightbox/puppet.git',
- 'https://github.com/gitlabhq/gitlabhq.git',
- 'https://github.com/gitlabhq/gitlab-ci.git',
- 'https://github.com/gitlabhq/gitlab-recipes.git',
- 'https://github.com/gitlabhq/gitlab-shell.git',
- 'https://github.com/gitlabhq/grack.git',
- 'https://github.com/twitter/flight.git',
- 'https://github.com/twitter/typeahead.js.git',
- 'https://github.com/h5bp/html5-boilerplate.git',
- 'https://github.com/h5bp/mobile-boilerplate.git',
-]
-
-project_urls.each_with_index do |url, i|
- group_path, project_path = url.split('/')[-2..-1]
-
- group = Group.find_by(path: group_path)
-
- unless group
- group = Group.new(
- name: group_path.titleize,
- path: group_path
- )
- group.description = Faker::Lorem.sentence
- group.owner = User.first
- group.save
- end
+Gitlab::Seeder.quiet do
+ project_urls = [
+ 'https://github.com/documentcloud/underscore.git',
+ 'https://github.com/diaspora/diaspora.git',
+ 'https://github.com/diaspora/diaspora-project-site.git',
+ 'https://github.com/diaspora/diaspora-client.git',
+ 'https://github.com/brightbox/brightbox-cli.git',
+ 'https://github.com/brightbox/puppet.git',
+ 'https://github.com/gitlabhq/gitlabhq.git',
+ 'https://github.com/gitlabhq/gitlab-ci.git',
+ 'https://github.com/gitlabhq/gitlab-recipes.git',
+ 'https://github.com/gitlabhq/gitlab-shell.git',
+ 'https://github.com/gitlabhq/grack.git',
+ 'https://github.com/gitlabhq/testme.git',
+ 'https://github.com/twitter/flight.git',
+ 'https://github.com/twitter/typeahead.js.git',
+ 'https://github.com/h5bp/html5-boilerplate.git',
+ 'https://github.com/h5bp/mobile-boilerplate.git',
+ ]
+
+ project_urls.each_with_index do |url, i|
+ group_path, project_path = url.split('/')[-2..-1]
+
+ group = Group.find_by(path: group_path)
+
+ unless group
+ group = Group.new(
+ name: group_path.titleize,
+ path: group_path
+ )
+ group.description = Faker::Lorem.sentence
+ group.save
+
+ group.add_owner(User.first)
+ end
- project_path.gsub!(".git", "")
+ project_path.gsub!(".git", "")
- params = {
- import_url: url,
- namespace_id: group.id,
- name: project_path.titleize,
- description: Faker::Lorem.sentence
- }
+ params = {
+ import_url: url,
+ namespace_id: group.id,
+ name: project_path.titleize,
+ description: Faker::Lorem.sentence
+ }
- project = Projects::CreateService.new(User.first, params).execute
+ project = Projects::CreateService.new(User.first, params).execute
- if project.valid?
- print '.'
- else
- puts project.errors.full_messages
- print 'F'
+ if project.valid?
+ print '.'
+ else
+ puts project.errors.full_messages
+ print 'F'
+ end
end
end
diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb
index 3440a645408..d736408dbf5 100644
--- a/db/fixtures/development/05_users.rb
+++ b/db/fixtures/development/05_users.rb
@@ -6,6 +6,7 @@ Gitlab::Seeder.quiet do
username: Faker::Internet.user_name,
name: Faker::Name.name,
email: Faker::Internet.email,
+ confirmed_at: DateTime.now
}])
print '.'
rescue ActiveRecord::RecordNotSaved
diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb
index a1e01879db5..dfbe75fd20e 100644
--- a/db/fixtures/development/06_teams.rb
+++ b/db/fixtures/development/06_teams.rb
@@ -1,5 +1,3 @@
-ActiveRecord::Base.observers.disable :all
-
Gitlab::Seeder.quiet do
Group.all.each do |group|
User.all.sample(4).each do |user|
diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb
index 2b81d7a2597..635878622d0 100644
--- a/db/fixtures/development/09_issues.rb
+++ b/db/fixtures/development/09_issues.rb
@@ -1,5 +1,3 @@
-ActiveRecord::Base.observers.disable :all
-
Gitlab::Seeder.quiet do
(1..300).each do |i|
# Random Project
@@ -12,9 +10,7 @@ Gitlab::Seeder.quiet do
user_id = user.id
- begin
- Thread.current[:current_user] = user
-
+ Gitlab::Seeder.by_user(user) do
Issue.seed(:id, [{
id: i,
project_id: project.id,
@@ -25,8 +21,6 @@ Gitlab::Seeder.quiet do
title: Faker::Lorem.sentence(6),
description: Faker::Lorem.sentence
}])
- ensure
- Thread.current[:current_user] = nil
end
print('.')
end
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 2b1f4160211..cb08a7c2537 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -1,5 +1,3 @@
-ActiveRecord::Base.observers.disable :all
-
Gitlab::Seeder.quiet do
(1..100).each do |i|
# Random Project
@@ -17,9 +15,8 @@ Gitlab::Seeder.quiet do
next if branches.uniq.size < 2
user_id = user.id
- begin
- Thread.current[:current_user] = user
+ Gitlab::Seeder.by_user(user) do
MergeRequest.seed(:id, [{
id: i,
source_branch: branches.first,
@@ -31,8 +28,6 @@ Gitlab::Seeder.quiet do
milestone: project.milestones.sample,
title: Faker::Lorem.sentence(6)
}])
- ensure
- Thread.current[:current_user] = nil
end
print('.')
end
diff --git a/db/fixtures/development/11_keys.rb b/db/fixtures/development/11_keys.rb
index 4b53ff411f2..61329f197c3 100644
--- a/db/fixtures/development/11_keys.rb
+++ b/db/fixtures/development/11_keys.rb
@@ -1,10 +1,8 @@
-ActiveRecord::Base.observers.enable :all
-
Gitlab::Seeder.quiet do
User.first(30).each_with_index do |user, i|
Key.seed(:id, [
{
- id: i,
+ id: i + 1,
title: "Sample key #{i}",
key: "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt#{i + 100}6k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
user_id: user.id,
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
index 4ca8afe294e..dced2706264 100644
--- a/db/fixtures/development/12_snippets.rb
+++ b/db/fixtures/development/12_snippets.rb
@@ -1,5 +1,3 @@
-ActiveRecord::Base.observers.disable :all
-
Gitlab::Seeder.quiet do
contents = [
`curl https://gist.github.com/randx/4275756/raw/da2f262920c96d1a970d48bf2e99147954b1f4bd/glus1204.sh`,
diff --git a/db/fixtures/development/13_comments.rb b/db/fixtures/development/13_comments.rb
new file mode 100644
index 00000000000..626aba200d1
--- /dev/null
+++ b/db/fixtures/development/13_comments.rb
@@ -0,0 +1,19 @@
+Gitlab::Seeder.quiet do
+ Issue.all.limit(10).each_with_index do |issue, i|
+ 5.times do
+ user = issue.project.team.users.sample
+
+ Gitlab::Seeder.by_user(user) do
+ Note.seed(:id, [{
+ project_id: issue.project.id,
+ author_id: user.id,
+ note: Faker::Lorem.sentence,
+ noteable_id: issue.id,
+ noteable_type: 'Issue'
+ }])
+
+ print '.'
+ end
+ end
+ end
+end
diff --git a/db/migrate/20130809124851_add_permission_check_to_user.rb b/db/migrate/20130809124851_add_permission_check_to_user.rb
new file mode 100644
index 00000000000..c26157904c7
--- /dev/null
+++ b/db/migrate/20130809124851_add_permission_check_to_user.rb
@@ -0,0 +1,5 @@
+class AddPermissionCheckToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :last_credential_check_at, :datetime
+ end
+end
diff --git a/db/migrate/20140209025651_create_emails.rb b/db/migrate/20140209025651_create_emails.rb
new file mode 100644
index 00000000000..cb78c4af11b
--- /dev/null
+++ b/db/migrate/20140209025651_create_emails.rb
@@ -0,0 +1,13 @@
+class CreateEmails < ActiveRecord::Migration
+ def change
+ create_table :emails do |t|
+ t.integer :user_id, null: false
+ t.string :email, null: false
+
+ t.timestamps
+ end
+
+ add_index :emails, :user_id
+ add_index :emails, :email, unique: true
+ end
+end
diff --git a/db/migrate/20140214102325_add_api_key_to_services.rb b/db/migrate/20140214102325_add_api_key_to_services.rb
new file mode 100644
index 00000000000..30eeca2c1f6
--- /dev/null
+++ b/db/migrate/20140214102325_add_api_key_to_services.rb
@@ -0,0 +1,5 @@
+class AddApiKeyToServices < ActiveRecord::Migration
+ def change
+ add_column :services, :api_key, :string
+ end
+end
diff --git a/db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb b/db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb
new file mode 100644
index 00000000000..65d28e8cb01
--- /dev/null
+++ b/db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb
@@ -0,0 +1,5 @@
+class AddIndexMergeRequestDiffsOnMergeRequestId < ActiveRecord::Migration
+ def change
+ add_index :merge_request_diffs, :merge_request_id, unique: true
+ end
+end
diff --git a/db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb b/db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb
new file mode 100644
index 00000000000..7017148702a
--- /dev/null
+++ b/db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb
@@ -0,0 +1,5 @@
+class AddTagPushHooksToProjectHook < ActiveRecord::Migration
+ def change
+ add_column :web_hooks, :tag_push_events, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20140312145357_add_import_status_to_project.rb b/db/migrate/20140312145357_add_import_status_to_project.rb
new file mode 100644
index 00000000000..ef972e8342a
--- /dev/null
+++ b/db/migrate/20140312145357_add_import_status_to_project.rb
@@ -0,0 +1,5 @@
+class AddImportStatusToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :import_status, :string
+ end
+end
diff --git a/db/migrate/20140313092127_migrate_already_imported_projects.rb b/db/migrate/20140313092127_migrate_already_imported_projects.rb
new file mode 100644
index 00000000000..f4392c0f05e
--- /dev/null
+++ b/db/migrate/20140313092127_migrate_already_imported_projects.rb
@@ -0,0 +1,12 @@
+class MigrateAlreadyImportedProjects < ActiveRecord::Migration
+ def up
+ Project.where(imported: true).update_all(import_status: "finished")
+ Project.where(imported: false).update_all(import_status: "none")
+ remove_column :projects, :imported
+ end
+
+ def down
+ add_column :projects, :imported, :boolean, default: false
+ Project.where(import_status: 'finished').update_all(imported: true)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index acbb793bbe8..d8a9d1863fc 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,15 +11,18 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20140127170938) do
+ActiveRecord::Schema.define(version: 20140313092127) do
+
+ # These are extensions that must be enabled in order to support this database
+ enable_extension "plpgsql"
create_table "broadcast_messages", force: true do |t|
t.text "message", null: false
t.datetime "starts_at"
t.datetime "ends_at"
t.integer "alert_type"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.string "color"
t.string "font"
end
@@ -27,20 +30,30 @@ ActiveRecord::Schema.define(version: 20140127170938) do
create_table "deploy_keys_projects", force: true do |t|
t.integer "deploy_key_id", null: false
t.integer "project_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
end
add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
+ create_table "emails", force: true do |t|
+ t.integer "user_id", null: false
+ t.string "email", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree
+ add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree
+
create_table "events", force: true do |t|
t.string "target_type"
t.integer "target_id"
t.string "title"
t.text "data"
t.integer "project_id"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.integer "action"
t.integer "author_id"
end
@@ -55,8 +68,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
create_table "forked_project_links", force: true do |t|
t.integer "forked_to_project_id", null: false
t.integer "forked_from_project_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
end
add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
@@ -96,14 +109,16 @@ ActiveRecord::Schema.define(version: 20140127170938) do
add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
create_table "merge_request_diffs", force: true do |t|
- t.string "state", default: "collected", null: false
- t.text "st_commits", limit: 2147483647
- t.text "st_diffs", limit: 2147483647
- t.integer "merge_request_id", null: false
+ t.string "state", default: "collected", null: false
+ t.text "st_commits"
+ t.text "st_diffs"
+ t.integer "merge_request_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
+ add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree
+
create_table "merge_requests", force: true do |t|
t.string "target_branch", null: false
t.string "source_branch", null: false
@@ -126,7 +141,7 @@ ActiveRecord::Schema.define(version: 20140127170938) do
add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
- add_index "merge_requests", ["source_project_id"], name: "index_merge_requests_on_project_id", using: :btree
+ add_index "merge_requests", ["source_project_id"], name: "index_merge_requests_on_source_project_id", using: :btree
add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree
add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree
@@ -135,8 +150,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
t.integer "project_id", null: false
t.text "description"
t.date "due_date"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.string "state"
t.integer "iid"
end
@@ -148,8 +163,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
t.string "name", null: false
t.string "path", null: false
t.integer "owner_id"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.string "type"
t.string "description", default: "", null: false
t.string "avatar"
@@ -171,8 +186,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
t.string "line_code"
t.string "commit_id"
t.integer "noteable_id"
- t.text "st_diff"
t.boolean "system", default: false, null: false
+ t.text "st_diff"
end
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
@@ -199,21 +214,21 @@ ActiveRecord::Schema.define(version: 20140127170938) do
t.string "issues_tracker_id"
t.boolean "snippets_enabled", default: true, null: false
t.datetime "last_activity_at"
- t.boolean "imported", default: false, null: false
t.string "import_url"
t.integer "visibility_level", default: 0, null: false
t.boolean "archived", default: false, null: false
+ t.string "import_status"
end
- add_index "projects", ["creator_id"], name: "index_projects_on_owner_id", using: :btree
+ add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree
add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree
add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
create_table "protected_branches", force: true do |t|
t.integer "project_id", null: false
t.string "name", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
end
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
@@ -223,27 +238,28 @@ ActiveRecord::Schema.define(version: 20140127170938) do
t.string "title"
t.string "token"
t.integer "project_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.boolean "active", default: false, null: false
t.string "project_url"
t.string "subdomain"
t.string "room"
t.text "recipients"
+ t.string "api_key"
end
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
create_table "snippets", force: true do |t|
t.string "title"
- t.text "content", limit: 2147483647
- t.integer "author_id", null: false
+ t.text "content"
+ t.integer "author_id", null: false
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "file_name"
t.datetime "expires_at"
- t.boolean "private", default: true, null: false
+ t.boolean "private", default: true, null: false
t.string "type"
end
@@ -262,17 +278,20 @@ ActiveRecord::Schema.define(version: 20140127170938) do
t.datetime "created_at"
end
+ add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree
+ add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
+
create_table "tags", force: true do |t|
t.string "name"
end
create_table "users", force: true do |t|
- t.string "email", default: "", null: false
- t.string "encrypted_password", limit: 128, default: "", null: false
+ t.string "email", default: "", null: false
+ t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
- t.integer "sign_in_count", default: 0
+ t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
@@ -280,39 +299,41 @@ ActiveRecord::Schema.define(version: 20140127170938) do
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
- t.boolean "admin", default: false, null: false
- t.integer "projects_limit", default: 10
- t.string "skype", default: "", null: false
- t.string "linkedin", default: "", null: false
- t.string "twitter", default: "", null: false
+ t.boolean "admin", default: false, null: false
+ t.integer "projects_limit", default: 10
+ t.string "skype", default: "", null: false
+ t.string "linkedin", default: "", null: false
+ t.string "twitter", default: "", null: false
t.string "authentication_token"
- t.integer "theme_id", default: 1, null: false
+ t.integer "theme_id", default: 1, null: false
t.string "bio"
- t.integer "failed_attempts", default: 0
+ t.integer "failed_attempts", default: 0
t.datetime "locked_at"
t.string "extern_uid"
t.string "provider"
t.string "username"
- t.boolean "can_create_group", default: true, null: false
- t.boolean "can_create_team", default: true, null: false
+ t.boolean "can_create_group", default: true, null: false
+ t.boolean "can_create_team", default: true, null: false
t.string "state"
- t.integer "color_scheme_id", default: 1, null: false
- t.integer "notification_level", default: 1, null: false
+ t.integer "color_scheme_id", default: 1, null: false
+ 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"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
- t.boolean "hide_no_ssh_key", default: false
- t.string "website_url", default: "", null: false
+ t.boolean "hide_no_ssh_key", default: false
+ t.string "website_url", default: "", null: false
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
+ add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
@@ -321,8 +342,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
t.integer "group_access", null: false
t.integer "group_id", null: false
t.integer "user_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.integer "notification_level", default: 3, null: false
end
@@ -351,6 +372,7 @@ ActiveRecord::Schema.define(version: 20140127170938) do
t.boolean "push_events", default: true, null: false
t.boolean "issues_events", default: false, null: false
t.boolean "merge_requests_events", default: false, null: false
+ t.boolean "tag_push_events", default: false
end
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 00000000000..865806f297a
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,17 @@
+## The GitLab Documentation covers the following subjects
+
++ [API](api/README.md)
++ [Development](development/README.md)
++ [Install](install/README.md)
++ [Integration](external-issue-tracker/README.md)
++ [Legal](legal/README.md)
++ [Markdown](markdown/markdown.md)
++ [Permissions](permissions/permissions.md)
++ [Public access](public_access/public_access.md)
++ [Raketasks](raketasks/README.md)
++ [Release](release/README.md)
++ [Security](security/README.md)
++ [System hooks](system_hooks/system_hooks.md)
++ [Update](update/README.md)
++ [Web hooks](web_hooks/web_hooks.md)
++ [Workflow](workflow/workflow.md)
diff --git a/doc/api/README.md b/doc/api/README.md
index 517a9fae6f6..fa5b12af14c 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -1,5 +1,31 @@
# GitLab API
+## End-points
+
++ [Users](users.md)
++ [Session](session.md)
++ [Projects](projects.md)
++ [Project Snippets](project_snippets.md)
++ [Repositories](repositories.md)
++ [Repository Files](repository_files.md)
++ [Commits](commits.md)
++ [Merge Requests](merge_requests.md)
++ [Issues](issues.md)
++ [Milestones](milestones.md)
++ [Notes](notes.md)
++ [Deploy Keys](deploy_keys.md)
++ [System Hooks](system_hooks.md)
++ [Groups](groups.md)
+
+## Clients
+
++ [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
++ [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
++ [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
++ [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
+
+## Introduction
+
All API requests require authentication. You need to pass a `private_token` parameter by url or header. If passed as header, the header name must be "PRIVATE-TOKEN" (capital and with dash instead of underscore). You can find or reset your private token in your profile.
If no, or an invalid, `private_token` is provided then an error message will be returned with status code 401:
@@ -117,28 +143,3 @@ Issue
So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json`
But when you want to create a link to web page - use `http:://host/project/issues/:iid.json`
-
-
-
-## Contents
-
-+ [Users](users.md)
-+ [Session](session.md)
-+ [Projects](projects.md)
-+ [Project Snippets](project_snippets.md)
-+ [Repositories](repositories.md)
-+ [Merge Requests](merge_requests.md)
-+ [Issues](issues.md)
-+ [Milestones](milestones.md)
-+ [Notes](notes.md)
-+ [Deploy Keys](deploy_keys.md)
-+ [System Hooks](system_hooks.md)
-+ [Groups](groups.md)
-
-
-## Clients
-
-+ [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
-+ [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
-+ [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
-+ [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
diff --git a/doc/api/commits.md b/doc/api/commits.md
new file mode 100644
index 00000000000..69b44a2e83b
--- /dev/null
+++ b/doc/api/commits.md
@@ -0,0 +1,95 @@
+# Commits API
+
+## List repository commits
+
+Get a list of repository commits in a project.
+
+```
+GET /projects/:id/repository/commits
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
+
+```json
+[
+ {
+ "id": "ed899a2f4b50b4370feeea94676502b42383c746",
+ "short_id": "ed899a2f4b5",
+ "title": "Replace sanitize with escape once",
+ "author_name": "Dmitriy Zaporozhets",
+ "author_email": "dzaporozhets@sphereconsultinginc.com",
+ "created_at": "2012-09-20T11:50:22+03:00"
+ },
+ {
+ "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
+ "short_id": "6104942438c",
+ "title": "Sanitize for network graph",
+ "author_name": "randx",
+ "author_email": "dmitriy.zaporozhets@gmail.com",
+ "created_at": "2012-09-20T09:06:12+03:00"
+ }
+]
+```
+
+## Get a single commit
+
+Get a specific commit identified by the commit hash or name of a branch or tag.
+
+```
+GET /projects/:id/repository/commits/:sha
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `sha` (required) - The commit hash or name of a repository branch or tag
+
+```json
+{
+ "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
+ "short_id": "6104942438c",
+ "title": "Sanitize for network graph",
+ "author_name": "randx",
+ "author_email": "dmitriy.zaporozhets@gmail.com",
+ "created_at": "2012-09-20T09:06:12+03:00",
+ "committed_date": "2012-09-20T09:06:12+03:00",
+ "authored_date": "2012-09-20T09:06:12+03:00",
+ "parent_ids" : [
+ "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
+ ]
+}
+```
+
+
+## Get the diff of a commit
+
+Get the diff of a commit in a project.
+
+```
+GET /projects/:id/repository/commits/:sha/diff
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `sha` (required) - The name of a repository branch or tag or if not given the default branch
+
+```json
+[
+ {
+ "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files",
+ "new_path": "doc/update/5.4-to-6.0.md",
+ "old_path": "doc/update/5.4-to-6.0.md",
+ "a_mode": null,
+ "b_mode": "100644",
+ "new_file": false,
+ "renamed_file": false,
+ "deleted_file": false
+ }
+]
+```
+
+
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 2ddaea5a584..3ce0fb84917 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -150,6 +150,7 @@ Parameters:
+ `target_branch` - The target branch
+ `assignee_id` - Assignee user ID
+ `title` - Title of MR
++ `state_event` - New state (close|reopen|merge)
```json
@@ -210,3 +211,44 @@ Parameters:
"note":"text1"
}
```
+
+
+## Get the comments on a MR
+
+Gets all the comments associated with a merge request.
+
+```
+GET /projects/:id/merge_request/:merge_request_id/comments
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `merge_request_id` (required) - ID of merge request
+
+```json
+[
+ {
+ "note":"this is the 1st comment on the 2merge merge request",
+ "author":{
+ "id":11,
+ "username":"admin",
+ "email":"admin@local.host",
+ "name":"Administrator",
+ "state":"active",
+ "created_at":"2014-03-06T08:17:35.000Z"
+ }
+ },
+ {
+ "note":"_Status changed to closed_",
+ "author":{
+ "id":11,
+ "username":"admin",
+ "email":"admin@local.host",
+ "name":"Administrator",
+ "state":"active",
+ "created_at":"2014-03-06T08:17:35.000Z"
+ }
+ }
+]
+```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 9f2945d0701..54618d7c045 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -148,6 +148,16 @@ Parameters:
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
}
+ "permissions": {
+ "project_access": {
+ "access_level": 10,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ }
}
```
@@ -231,6 +241,7 @@ POST /projects
Parameters:
+ `name` (required) - new project name
++ `namespace_id` (optional) - namespace for the new project (defaults to user)
+ `description` (optional) - short project description
+ `issues_enabled` (optional)
+ `wall_enabled` (optional)
@@ -610,3 +621,29 @@ 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
+
+
+## Labels
+
+### List project labels
+
+Get a list of project labels.
+
+```
+GET /projects/:id/labels
+```
+
+Parameters:
+
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+
+```json
+[
+ {
+ "name":"featute"
+ },
+ {
+ "name": "bug"
+ }
+]
+```
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 01607263008..65ea3615354 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -204,99 +204,6 @@ Parameters:
]
```
-
-## List repository commits
-
-Get a list of repository commits in a project.
-
-```
-GET /projects/:id/repository/commits
-```
-
-Parameters:
-
-+ `id` (required) - The ID of a project
-+ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
-
-```json
-[
- {
- "id": "ed899a2f4b50b4370feeea94676502b42383c746",
- "short_id": "ed899a2f4b5",
- "title": "Replace sanitize with escape once",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dzaporozhets@sphereconsultinginc.com",
- "created_at": "2012-09-20T11:50:22+03:00"
- },
- {
- "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
- "short_id": "6104942438c",
- "title": "Sanitize for network graph",
- "author_name": "randx",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "created_at": "2012-09-20T09:06:12+03:00"
- }
-]
-```
-
-## Get a single commit
-
-Get a specific commit identified by the commit hash or name of a branch or tag.
-
-```
-GET /projects/:id/repository/commits/:sha
-```
-
-Parameters:
-
-+ `id` (required) - The ID of a project
-+ `sha` (required) - The commit hash or name of a repository branch or tag
-
-```json
-{
- "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
- "short_id": "6104942438c",
- "title": "Sanitize for network graph",
- "author_name": "randx",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "created_at": "2012-09-20T09:06:12+03:00",
- "committed_date": "2012-09-20T09:06:12+03:00",
- "authored_date": "2012-09-20T09:06:12+03:00",
- "parent_ids" : [
- "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
- ]
-}
-```
-
-
-## Get the diff of a commit
-
-Get the diff of a commit in a project.
-
-```
-GET /projects/:id/repository/commits/:sha/diff
-```
-
-Parameters:
-
-+ `id` (required) - The ID of a project
-+ `sha` (required) - The name of a repository branch or tag or if not given the default branch
-
-```json
-[
- {
- "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files",
- "new_path": "doc/update/5.4-to-6.0.md",
- "old_path": "doc/update/5.4-to-6.0.md",
- "a_mode": null,
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false
- }
-]
-```
-
## List repository tree
Get a list of repository files and directories in a project.
@@ -388,44 +295,3 @@ GET /projects/:id/repository/archive
Parameters:
+ `id` (required) - The ID of a project
+ `sha` (optional) - The commit sha to download defaults to the tip of the default branch
-
-
-## Create new file in repository
-
-```
-POST /projects/:id/repository/files
-```
-
-Parameters:
-
-+ `file_path` (optional) - Full path to new file. Ex. lib/class.rb
-+ `branch_name` (required) - The name of branch
-+ `encoding` (optional) - 'text' or 'base64'. Text is default.
-+ `content` (required) - File content
-+ `commit_message` (required) - Commit message
-
-## Update existing file in repository
-
-```
-PUT /projects/:id/repository/files
-```
-
-Parameters:
-
-+ `file_path` (required) - Full path to file. Ex. lib/class.rb
-+ `branch_name` (required) - The name of branch
-+ `encoding` (optional) - 'text' or 'base64'. Text is default.
-+ `content` (required) - New file content
-+ `commit_message` (required) - Commit message
-
-## Delete existing file in repository
-
-```
-DELETE /projects/:id/repository/files
-```
-
-Parameters:
-
-+ `file_path` (required) - Full path to file. Ex. lib/class.rb
-+ `branch_name` (required) - The name of branch
-+ `commit_message` (required) - Commit message
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
new file mode 100644
index 00000000000..cafab8c828f
--- /dev/null
+++ b/doc/api/repository_files.md
@@ -0,0 +1,102 @@
+# CRUD for repository files
+
+## Create, read, update and delete repository files using this API
+
+- - -
+
+## Get file from repository
+
+Allows you to receive information about file in repository like name, size, content.
+Note that file content is Base64 encoded.
+
+```
+GET /projects/:id/repository/files
+```
+
+Example response:
+
+```json
+{
+ "file_name": "key.rb",
+ "file_path": "app/models/key.rb",
+ "size": 1476,
+ "encoding": "base64",
+ "content": "IyA9PSBTY2hlbWEgSW5mb3...",
+ "ref": "master",
+ "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83",
+ "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50"
+}
+```
+
+Parameters:
+
++ `file_path` (required) - Full path to new file. Ex. lib/class.rb
++ `ref` (required) - The name of branch, tag or commit
+
+## Create new file in repository
+
+```
+POST /projects/:id/repository/files
+```
+
+Example response:
+
+```json
+{
+ "file_name": "app/project.rb",
+ "branch_name": "master",
+}
+```
+
+Parameters:
+
++ `file_path` (required) - Full path to new file. Ex. lib/class.rb
++ `branch_name` (required) - The name of branch
++ `encoding` (optional) - 'text' or 'base64'. Text is default.
++ `content` (required) - File content
++ `commit_message` (required) - Commit message
+
+## Update existing file in repository
+
+```
+PUT /projects/:id/repository/files
+```
+
+Example response:
+
+```json
+{
+ "file_name": "app/project.rb",
+ "branch_name": "master",
+}
+```
+
+Parameters:
+
++ `file_path` (required) - Full path to file. Ex. lib/class.rb
++ `branch_name` (required) - The name of branch
++ `encoding` (optional) - 'text' or 'base64'. Text is default.
++ `content` (required) - New file content
++ `commit_message` (required) - Commit message
+
+## Delete existing file in repository
+
+```
+DELETE /projects/:id/repository/files
+```
+
+Example response:
+
+```json
+{
+ "file_name": "app/project.rb",
+ "branch_name": "master",
+}
+```
+
+Parameters:
+
++ `file_path` (required) - Full path to file. Ex. lib/class.rb
++ `branch_name` (required) - The name of branch
++ `commit_message` (required) - Commit message
+
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index 355ce31c126..2e87ada1d70 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -1,70 +1,70 @@
-All methods require admin authorization.
-
-The url endpoint of the system hooks can be configured in [the admin area under hooks](/admin/hooks).
-
-## List system hooks
-
-Get list of system hooks
-
-```
-GET /hooks
-```
-
-Parameters:
-
-+ **none**
-
-```json
-[
- {
- "id":3,
- "url":"http://example.com/hook",
- "created_at":"2013-10-02T10:15:31Z"
- }
-]
-```
-
-## Add new system hook hook
-
-```
-POST /hooks
-```
-
-Parameters:
-
-+ `url` (required) - The hook URL
-
-
-## Test system hook
-
-```
-GET /hooks/:id
-```
-
-Parameters:
-
-+ `id` (required) - The ID of hook
-
-```json
-{
- "event_name":"project_create",
- "name":"Ruby",
- "path":"ruby",
- "project_id":1,
- "owner_name":"Someone",
- "owner_email":"example@gitlabhq.com"
-}
-```
-
-## Delete system hook
-
-Deletes a system hook. This is an idempotent API function and returns `200 Ok` even if the hook
-is not available. If the hook is deleted it is also returned as JSON.
-
-```
-DELETE /hooks/:id
-```
-
-Parameters:
-
-+ `id` (required) - The ID of hook
+All methods require admin authorization.
+
+The url endpoint of the system hooks can be configured in [the admin area under hooks](/admin/hooks).
+
+## List system hooks
+
+Get list of system hooks
+
+```
+GET /hooks
+```
+
+Parameters:
+
++ **none**
+
+```json
+[
+ {
+ "id":3,
+ "url":"http://example.com/hook",
+ "created_at":"2013-10-02T10:15:31Z"
+ }
+]
+```
+
+## Add new system hook hook
+
+```
+POST /hooks
+```
+
+Parameters:
+
++ `url` (required) - The hook URL
+
+
+## Test system hook
+
+```
+GET /hooks/:id
+```
+
+Parameters:
+
++ `id` (required) - The ID of hook
+
+```json
+{
+ "event_name":"project_create",
+ "name":"Ruby",
+ "path":"ruby",
+ "project_id":1,
+ "owner_name":"Someone",
+ "owner_email":"example@gitlabhq.com"
+}
+```
+
+## Delete system hook
+
+Deletes a system hook. This is an idempotent API function and returns `200 Ok` even if the hook
+is not available. If the hook is deleted it is also returned as JSON.
+
+```
+DELETE /hooks/:id
+```
+
+Parameters:
+
++ `id` (required) - The ID of hook
diff --git a/doc/development/README.md b/doc/development/README.md
new file mode 100644
index 00000000000..aa59eb2c3e1
--- /dev/null
+++ b/doc/development/README.md
@@ -0,0 +1,2 @@
++ [Architecture](architecture.md)
++ [Shell commands](shell_commands.md)
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 8a772344b79..6f832614d70 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -3,7 +3,7 @@
# Software delivery
-There are two editions of GitLab: [Enterprise Edition](https://www.gitlab.com/features/) (EE) and [Community Edition](http://gitlab.org/gitlab-ce/) (CE).
+There are two editions of GitLab: [Enterprise Edition](https://www.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://www.gitlab.com/gitlab-ce/) (CE).
GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master).
New versions of GitLab are released in stable branches and the master branch is for bleeding edge development.
@@ -18,7 +18,7 @@ New releases are generally around the same time as GitLab CE releases with excep
# System Layout
-When referring to ~git in the picures it means the home directory of the git user which is typically /home/git.
+When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git.
GitLab is primarily installed within the `/home/git` user home directory as `git` user.
Within the home directory is where the gitlabhq server software resides as well as the repositories (though the repository location is configurable).
@@ -28,7 +28,7 @@ To serve repositories over SSH there's an add-on application called gitlab-shell
## Components
-![GitLab Diagram Overview](resources/gitlab_diagram_overview.png "GitLab Diagram Overview")
+![GitLab Diagram Overview](resources/gitlab_diagram_overview.png)
A typical install of GitLab will be on Ubuntu Linux or RHEL/CentOS.
It uses Nginx or Apache as a web front end to proxypass the Unicorn web server.
@@ -180,4 +180,4 @@ bundle exec rake gitlab:check RAILS_ENV=production
```
Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`.
-While the sudo commands provided by gitlabhq work in Ubuntu they do not always work in RHEL.
+While the sudo commands provided by gitlabhq work in Ubuntu they do not always work in RHEL. \ No newline at end of file
diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md
new file mode 100644
index 00000000000..af0d5ca4426
--- /dev/null
+++ b/doc/development/shell_commands.md
@@ -0,0 +1,117 @@
+# Guidelines for shell commands in the GitLab codebase
+
+## References
+
+- [Google Ruby Security Reviewer's Guide](https://code.google.com/p/ruby-security/wiki/Guide)
+- [OWASP Command Injection](https://www.owasp.org/index.php/Command_Injection)
+- [Ruby on Rails Security Guide Command Line Injection](http://guides.rubyonrails.org/security.html#command-line-injection)
+
+## Use File and FileUtils instead of shell commands
+
+Sometimes we invoke basic Unix commands via the shell when there is also a Ruby API for doing it.
+Use the Ruby API if it exists.
+http://www.ruby-doc.org/stdlib-2.0.0/libdoc/fileutils/rdoc/FileUtils.html#module-FileUtils-label-Module+Functions
+
+```ruby
+# Wrong
+system "mkdir -p tmp/special/directory"
+# Better (separate tokens)
+system *%W(mkdir -p tmp/special/directory)
+# Best (do not use a shell command)
+FileUtils.mkdir_p "tmp/special/directory"
+
+# Wrong
+contents = `cat #{filename}`
+# Correct
+contents = File.read(filename)
+```
+
+This coding style could have prevented CVE-2013-4490.
+
+## Bypass the shell by splitting commands into separate tokens
+
+When we pass shell commands as a single string to Ruby, Ruby will let `/bin/sh` evaluate the entire string.
+Essentially, we are asking the shell to evaluate a one-line script.
+This creates a risk for shell injection attacks.
+It is better to split the shell command into tokens ourselves.
+Sometimes we use the scripting capabilities of the shell to change the working directory or set environment variables.
+All of this can also be achieved securely straight from Ruby
+
+```ruby
+# Wrong
+system "cd /home/git/gitlab && bundle exec rake db:#{something} RAILS_ENV=production"
+# Correct
+system({'RAILS_ENV' => 'production'}, *%W(bundle exec rake db:#{something}), chdir: '/home/git/gitlab')
+
+# Wrong
+system "touch #{myfile}"
+# Better
+system "touch", myfile
+# Best (do not run a shell command at all)
+FileUtils.touch myfile
+```
+
+This coding style could have prevented CVE-2013-4546.
+
+## Separate options from arguments with --
+
+Make the difference between options and arguments clear to the argument parsers of system commands with `--`.
+This is supported by many but not all Unix commands.
+
+To understand what `--` does, consider the problem below.
+
+```
+# Example
+$ echo hello > -l
+$ cat -l
+cat: illegal option -- l
+usage: cat [-benstuv] [file ...]
+```
+
+In the example above, the argument parser of `cat` assumes that `-l` is an option.
+The solution in the example above is to make it clear to `cat` that `-l` is really an argument, not an option.
+Many Unix command line tools follow the convention of separating options from arguments with `--`.
+
+```
+# Example (continued)
+$ cat -- -l
+hello
+```
+
+In the GitLab codebase, we avoid the option/argument ambiguity by _always_ using `--`.
+
+```ruby
+# Wrong
+system(*%W(git branch -d #{branch_name}))
+# Correct
+system(*%W(git branch -d -- #{branch_name}))
+```
+
+This coding style could have prevented CVE-2013-4582.
+
+## Do not use the backticks
+
+Capturing the output of shell commands with backticks reads nicely, but you are forced to pass the command as one string to the shell.
+We explained above that this is unsafe.
+In the main GitLab codebase, the solution is to use `Gitlab::Popen.popen` instead.
+
+```ruby
+# Wrong
+logs = `cd #{repo_dir} && git log`
+# Correct
+logs, exit_status = Gitlab::Popen.popen(%W(git log), repo_dir)
+
+# Wrong
+user = `whoami`
+# Correct
+user, exit_status = Gitlab::Popen.popen(%W(whoami))
+```
+
+In other repositories, such as gitlab-shell you can also use `IO.popen`.
+
+```ruby
+# Safe IO.popen example
+logs = IO.popen(%W(git log), chdir: repo_dir).read
+```
+
+Note that unlike `Gitlab::Popen.popen`, `IO.popen` does not capture standard error.
diff --git a/doc/install/README.md b/doc/install/README.md
new file mode 100644
index 00000000000..ec80e3cd62a
--- /dev/null
+++ b/doc/install/README.md
@@ -0,0 +1,4 @@
++ [Installation](installation.md)
++ [Requirements](requirements.md)
++ [Structure](structure.md)
++ [Database MySQL](database_mysql.md)
diff --git a/doc/install/databases.md b/doc/install/database_mysql.md
index 481a698a8c8..bf8183729e7 100644
--- a/doc/install/databases.md
+++ b/doc/install/database_mysql.md
@@ -1,15 +1,14 @@
-# Setup Database
-
-GitLab supports the following databases:
-
-* MySQL (preferred)
-* PostgreSQL
+## Note
+We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](http://bugs.mysql.com/bug.php?id=65830) that [suggested](http://bugs.mysql.com/bug.php?id=50909) [fixes](http://bugs.mysql.com/bug.php?id=65830) [have](http://bugs.mysql.com/bug.php?id=63164).
## MySQL
# Install the database packages
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
+
+ # Ensure you have MySQL version 5.5.14 or later
+ mysql --version
# Pick a database root password (can be anything), type it and press enter
# Retype the database root password and press enter
@@ -27,6 +26,10 @@ GitLab supports the following databases:
# change $password in the command below to a real password you pick
mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
+ # Ensure you can use the InnoDB engine which is necessary to support long indexes.
+ # If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off"
+ mysql> SET storage_engine=INNODB;
+
# Create the GitLab production database
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
@@ -47,25 +50,3 @@ GitLab supports the following databases:
mysql> \q
# You are done installing the database and can go back to the rest of the installation.
-
-
-## PostgreSQL
-
- # Install the database packages
- sudo apt-get install -y postgresql-9.1 postgresql-client libpq-dev
-
- # Login to PostgreSQL
- sudo -u postgres psql -d template1
-
- # Create a user for GitLab.
- template1=# CREATE USER git;
-
- # Create the GitLab production database & grant all privileges on database
- template1=# CREATE DATABASE gitlabhq_production OWNER git;
-
- # Quit the database session
- template1=# \q
-
- # Try connecting to the new database with the new user
- sudo -u git -H psql -d gitlabhq_production
-
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 8cee78b03d5..addb21b50e0 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -4,7 +4,7 @@ this should be the highest numbered stable branch (example shown below).
![capture](http://i.imgur.com/d2AlIVj.png)
-If this is unclear check the [GitLab Blog](http://blog.gitlab.org/) for installation guide links by version.
+If this is unclear check the [GitLab Blog](https://www.gitlab.com/blog/) for installation guide links by version.
# Important notes
@@ -128,7 +128,7 @@ GitLab Shell is an ssh access and repository management software developed speci
cd /home/git
# Clone gitlab shell
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-shell.git -b v1.8.0
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-shell.git -b v1.9.1
cd gitlab-shell
@@ -144,7 +144,25 @@ GitLab Shell is an ssh access and repository management software developed speci
# 5. Database
-To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install/databases.md`](./databases.md).
+We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md).
+
+ # Install the database packages
+ sudo apt-get install -y postgresql-9.1 postgresql-client libpq-dev
+
+ # Login to PostgreSQL
+ sudo -u postgres psql -d template1
+
+ # Create a user for GitLab.
+ template1=# CREATE USER git;
+
+ # Create the GitLab production database & grant all privileges on database
+ template1=# CREATE DATABASE gitlabhq_production OWNER git;
+
+ # Quit the database session
+ template1=# \q
+
+ # Try connecting to the new database with the new user
+ sudo -u git -H psql -d gitlabhq_production
# 6. GitLab
@@ -155,13 +173,13 @@ To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install
## Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 6-5-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 6-7-stable gitlab
# Go to gitlab dir
cd /home/git/gitlab
**Note:**
-You can change `6-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+You can change `6-6-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
## Configure it
@@ -216,8 +234,8 @@ Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup.
## Configure GitLab DB settings
- # Mysql
- sudo -u git cp config/database.yml.mysql config/database.yml
+ # PostgreSQL
+ sudo -u git cp config/database.yml.postgresql config/database.yml
# Make sure to update username/password in config/database.yml.
# You only need to adapt the production settings (first part).
@@ -227,10 +245,8 @@ Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup.
sudo -u git -H editor config/database.yml
or
-
- # PostgreSQL
- sudo -u git cp config/database.yml.postgresql config/database.yml
-
+ # Mysql
+ sudo -u git cp config/database.yml.mysql config/database.yml
# Make config/database.yml readable to git only
sudo -u git -H chmod o-rwx config/database.yml
@@ -239,12 +255,12 @@ Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup.
cd /home/git/gitlab
- # For MySQL (note, the option says "without ... postgres")
- sudo -u git -H bundle install --deployment --without development test postgres aws
-
- # Or for PostgreSQL (note, the option says "without ... mysql")
+ # For PostgreSQL (note, the option says "without ... mysql")
sudo -u git -H bundle install --deployment --without development test mysql aws
+ # Or if you use MySQL (note, the option says "without ... postgres")
+ sudo -u git -H bundle install --deployment --without development test postgres aws
+
## Initialize Database and Activate Advanced Features
@@ -335,7 +351,7 @@ If all items are green, then congratulations on successfully installing GitLab!
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:
- admin@local.host
+ root
5iveL!fe
**Important Note:**
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index ea172733b11..56a2a5efeb0 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -2,26 +2,24 @@
GitLab is developed for the Linux operating system. For the installations options and instructions please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation).
-## GitLab officially supports
+## Supported Linux distributions
-- Ubuntu Linux
-- Debian/GNU Linux
-
-## GitLab.com offers paid support for
-
-- Red Hat Enterprise Linux (RHEL)
+- Ubuntu
+- Debian
- CentOS
+- RedHat Enterprise Linux
+- Scientific Linux
- Oracle Linux
-## Not officially supported are
+## Unsupported Linux distributions
- Arch Linux
- Fedora
- Gentoo
-But on the above distributions it is pretty easy to install GitLab yourself.
+But on the above unsupported distributions is stll possible to install GitLab yourself with the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md).
-## Unsupported Unix Systems
+## Unsupported Unix operating systems
There is nothing that prevents GitLab from running on other Unix operating systems.
This means you may get it to work on systems running FreeBSD or OS X.
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 02eadfd410a..3212ebd64b5 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -5,3 +5,5 @@ GitLab has a great issue tracker but you can also use an external issue tracker
- textual references to PROJECT-1234 in comments, commit messages get turned into HTML links to the corresponding JIRA issue.
![jira screenshot](jira-intergration-points.png)
+
+You can configure the integration in the gitlab.yml configuration file.
diff --git a/doc/legal/README.md b/doc/legal/README.md
new file mode 100644
index 00000000000..ebfdad13540
--- /dev/null
+++ b/doc/legal/README.md
@@ -0,0 +1,2 @@
++ [Corporate contributor license agreement](corporate_contributor_license_agreement.md)
++ [Individual contributor license agreement](individual_contributor_license_agreement.md)
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index f49f09756ab..e7ebc613431 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -8,23 +8,38 @@ Table of Contents
**[GitLab Flavored Markdown](#gitlab-flavored-markdown-gfm)**
[Newlines](#newlines)
+
[Multiple underscores in words](#multiple-underscores-in-words)
+
[URL autolinking](#url-autolinking)
+
[Code and Syntax Highlighting](#code-and-syntax-highlighting)
+
[Emoji](#emoji)
+
[Special GitLab references](#special-gitlab-references)
+
**[Standard Markdown](#standard-markdown)**
[Headers](#headers)
+
[Emphasis](#emphasis)
+
[Lists](#lists)
+
[Links](#links)
+
[Images](#images)
+
[Blockquotes](#blockquotes)
+
[Inline HTML](#inline-html)
+
[Horizontal Rule](#horizontal-rule)
+
[Line Breaks](#line-breaks)
+
[Tables](#tables)
**[References](#references)**
@@ -33,7 +48,8 @@ Table of Contents
GitLab Flavored Markdown (GFM)
==============================
-For GitLab we developed something we call "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality.
+For GitLab we developed something we call "GitLab Flavored Markdown" (GFM).
+It extends the standard Markdown in a few significant ways to add some useful functionality.
You can use GFM in
@@ -51,16 +67,20 @@ Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#mar
Newlines
--------
-The biggest difference that GFM introduces is in the handling of linebreaks. With traditional Markdown you can hard wrap paragraphs of text and they will be combined into a single paragraph. We find this to be the cause of a huge number of unintentional formatting errors. GFM treats newlines in paragraph-like content as real line breaks, which is probably what you intended.
+GFM honors the markdown specification in how [paragraphs and line breaks are handled](http://daringfireball.net/projects/markdown/syntax#p).
-The next paragraph contains two phrases separated by a single newline character:
+A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines.:
Roses are red
Violets are blue
+ Sugar is sweet
+
Roses are red
Violets are blue
+Sugar is sweet
+
Multiple underscores in words
-----------------------------
It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words.
@@ -319,13 +339,13 @@ Strikethrough uses two tildes. ~~Scratch this.~~
## Links
-There are two ways to create links.
+There are two ways to create links, inline-style and reference-style.
[I'm an inline-style link](https://www.google.com)
[I'm a reference-style link][Arbitrary case-insensitive reference text]
- [I'm a relative reference to a repository file](../blob/master/LICENSE)
+ [I'm a relative reference to a repository file](LICENSE)
[You can use numbers for reference-style link definitions][1]
@@ -341,7 +361,7 @@ There are two ways to create links.
[I'm a reference-style link][Arbitrary case-insensitive reference text]
-[I'm a relative reference to a repository file](../blob/master/LICENSE)
+[I'm a relative reference to a repository file](LICENSE)
[You can use numbers for reference-style link definitions][1]
@@ -353,6 +373,15 @@ Some text to show that the reference links can follow later.
[1]: http://slashdot.org
[link text itself]: http://www.reddit.com
+**Note**
+
+Relative links do not allow referencing project files in a wiki page or wiki page in a project file.
+The reason for this is that, in GitLab, wiki is always a separate git repository. For example:
+
+`[I'm a reference-style link][style]`
+
+will point the link to `wikis/style` when the link is inside of a wiki markdown file.
+
## Images
Here's our logo (hover to see the title text):
@@ -365,15 +394,15 @@ Some text to show that the reference links can follow later.
[logo]: assets/logo-white.png
-Here's our logo (hover to see the title text):
+Here's our logo:
Inline-style:
-![alt text](/assets/logo-white.png "Logo Title Text 1")
+![alt text](/assets/logo-white.png)
Reference-style:
![alt text][logo]
-[logo]: /assets/logo-white.png "Logo Title Text 2"
+[logo]: /assets/logo-white.png
## Blockquotes
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
new file mode 100644
index 00000000000..ac4bdefddd5
--- /dev/null
+++ b/doc/permissions/permissions.md
@@ -0,0 +1,45 @@
+Users have different abilities depending on the access level they have in a particular group or project.
+If a user is both in a project group and in the project itself, the highest permission level is used.
+If a user is a GitLab administrator they receive all permissions.
+
+---
+
+#### Project:
+
+
+| Action| Guest | Reporter | Developer | Master | Owner|
+|-------|-------|----------|-----------|--------|------|
+|Create new issue|✓|✓|✓|✓|✓|
+|Leave comments|✓|✓|✓|✓|✓|
+|Write on project wall|✓|✓|✓|✓|✓|
+|Pull project code| |✓|✓|✓|✓|
+|Download project| |✓|✓|✓|✓|
+|Create code snippets| |✓|✓|✓|✓|
+|Create new merge request| ||✓|✓|✓|
+|Create new branches| ||✓|✓|✓|
+|Push to non-protected branches| ||✓|✓|✓|
+|Remove non-protected branches| ||✓|✓|✓|
+|Add tags| ||✓|✓|✓|
+|Write a wiki| ||✓|✓|✓|
+|Manage issue tracker| ||✓|✓|✓|
+|Add new team members| |||✓|✓|
+|Push to protected branches| |||✓|✓|
+|Remove protected branches| |||✓|✓|
+|Edit project| |||✓|✓|
+|Add Deploy Keys to project| |||✓|✓|
+|Confiure Project Hooks| |||✓|✓|
+|Switch visibility level| ||||✓|
+|Transfer project to another namespace| ||||✓|
+|Remove project| ||||✓|
+
+#### Group
+
+|Action|Guest|Reporter|Developer|Master|Owner|
+|------|-----|--------|---------|------|-----|
+|Browse group|✓|✓|✓|✓|✓|
+|Edit group|||||✓|
+|Create project in group|||||✓|
+|Manage group members|||||✓|
+|Remove group|||||✓|
+
+Any user can remove himself from a group, unless he is the last Owner of the group.
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
new file mode 100644
index 00000000000..bf9d2784aff
--- /dev/null
+++ b/doc/public_access/public_access.md
@@ -0,0 +1,28 @@
+Gitlab allows you to open selected projects to be accessed **publicly** or **internally**.
+Projects with either of these visibility levels will be listen in the [public access directory](/public).
+Internal projects will only be available to authenticated users.
+
+#### Public projects
+Public projects can be cloned **without any** authentication.
+It will also be listen on the [public access directory](/public).
+**Any logged in user** will have [Guest](/help/permissions) permissions on the repository.
+
+#### Internal projects
+Internal projects can be cloned by any logged in user.
+It will also be listed on the [public access directory](/public) for logged in users.
+Any logged in user will have [Guest](/help/permissions) permissions on the repository.
+
+#### How to change project visibility
+1. Go to your project dashboard
+2. Click on the "Edit" tab
+3. Change "Visibility Level"
+
+#### Visibility of users
+The public page of users, located at `/u/username` is visible if either:
+
+* You are logged in.
+* You are logged out, and the target user is authorized to (is Guest, Reporter, etc.) at least one public project.
+
+Otherwise, you will be redirected to the sign in page.
+
+When visiting the public page of an user, you will only see listed projects which you can view yourself.
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
new file mode 100644
index 00000000000..9aa80af12cc
--- /dev/null
+++ b/doc/raketasks/README.md
@@ -0,0 +1,6 @@
++ [Backup restore](backup_restore.md)
++ [Cleanup](cleanup.md)
++ [Features](features.md)
++ [Maintenance](maintenance.md)
++ [User management](user_management.md)
++ [Web hooks](web_hooks.md)
diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md
index 38474104852..e8232082916 100644
--- a/doc/raketasks/user_management.md
+++ b/doc/raketasks/user_management.md
@@ -15,7 +15,7 @@ Notes:
bundle exec rake gitlab:import:all_users_to_all_projects
```
-### Add user as a developer to all projects
+### Add user as a developer to all groups
```
bundle exec rake gitlab:import:user_to_groups[username@domain.tld]
diff --git a/doc/release/README.md b/doc/release/README.md
new file mode 100644
index 00000000000..22510be3f18
--- /dev/null
+++ b/doc/release/README.md
@@ -0,0 +1,2 @@
++ [Monthly](monthly.md)
++ [Security](security.md)
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index 62bb0f7b40a..08149b4da86 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -58,16 +58,14 @@ Check if changed since last release (~22nd of last month depending on when last
After making the release branch new commits are cherry-picked from master. When the release gets closer we get more selective what is cherry-picked. The days of the month are approximately as follows:
-* 17th: feature freeze (stop merging new features in master)
-* 18th: UI freeze (stop merging changes to the user interface)
-* 19th: code freeze (stop merging non-essential code improvements)
-* 20th: release candidate 1 (VERSION x.x.0.pre, tag and tweet about x.x.0.rc1)
-* 21st: optional release candidate 2 (x.x.0.rc2, only if rc1 had problems)
+* 1-7th: official merge window (see contributing guide)
+* 8-14th: work on bugfixes, sponsored features and GitLab EE
+* 15th: code freeze (stop merging into master except essential bugfixes)
+* 18th: release candidate 1 (VERSION x.x.0.rc1, tag and tweet about x.x.0.rc1, release on GitLab Cloud)
+* 20st: optional release candidate 2 (x.x.0.rc2, only if rc1 had problems)
* 22nd: release (VERSION x.x.0, create x-x-stable branch, tag, blog and tweet)
* 23nd: optional patch releases (x.x.1, x.x.2, etc., only if there are serious problems)
-* 24-end of month: release Enterprise Edition and upgrade GitLab Cloud
-* 1-7th: official merge window (see contributing guide)
-* 8-16th: bugfixes and sponsored features
+* 24-end of month: release GitLab EE and GitLab CI
# Write a blog post
diff --git a/doc/release/security.md b/doc/release/security.md
index 7ec3991de85..8e5a7e32099 100644
--- a/doc/release/security.md
+++ b/doc/release/security.md
@@ -15,7 +15,7 @@ Please report suspected security vulnerabilities in private to support@gitlab.co
1. Acknowledge the issue to the researcher that disclosed it
1. Fix the issue on a feature branch, do this on the private GitLab development server and update the VERSION and CHANGELOG in this branch
1. Consider creating and testing workarounds
-1. Create feature branches for the blog posts on GitLab.org and GitLab.com and link them from the code branch
+1. Create feature branches for the blog post on GitLab.com and link them from the code branch
1. Merge the code feature branch into master
1. Cherry-pick the code into the latest stable branch
1. Create a git tag vX.X.X for CE and another patch release for EE
diff --git a/doc/security/README.md b/doc/security/README.md
new file mode 100644
index 00000000000..f8dd1291b9b
--- /dev/null
+++ b/doc/security/README.md
@@ -0,0 +1,2 @@
++ [Password length limits](password_length_limits.md)
++ [Rack attack](rack_attack.md)
diff --git a/doc/ssh/deploy_keys.md b/doc/ssh/deploy_keys.md
new file mode 100644
index 00000000000..56fe98bb101
--- /dev/null
+++ b/doc/ssh/deploy_keys.md
@@ -0,0 +1,12 @@
+Deploy keys allow read-only access one or multiple projects with a single SSH key.
+
+This is really useful for cloning repositories to your Continuous Integration (CI) server.
+By using a deploy keys you don't have to setup a dummy user account.
+
+If you are a project master or owner you can add a deploy key in the project settings under the section Deploy Keys.
+Press the 'New Deploy Key' button and upload a public ssh key.
+After this the machine that uses the corresponding private key has read-only access to the project.
+
+You can't add the same deploy key twice with the 'New Deploy Key' option.
+If you want to add the same key to another project please enable it in the list that says 'Deploy keys from projects available to you'.
+You need to be the owner of the deploy key to see it in this list.
diff --git a/doc/ssh/ssh.md b/doc/ssh/ssh.md
new file mode 100644
index 00000000000..0a38bc16b49
--- /dev/null
+++ b/doc/ssh/ssh.md
@@ -0,0 +1,24 @@
+SSH key allows you to establish a secure connection between your computer and GitLab
+
+
+Before generating an SSH key, check if your system already has one by running `cat ~/.ssh/id_rsa.pub`
+If your see a long string starting with `ssh-rsa` or `ssh-dsa`, you can skip the ssh-keygen step.
+
+
+To generate a new SSH key just open your terminal and use code below. The ssh-keygen command prompts you for a location and filename to store the key pair and for a password.
+When prompted for the location and filename you can press enter to use the default.
+It is a best practice to use a password for an SSH key but it is not required and you can skip creating a password by pressing enter.
+Note that the password you choose here can't be altered or retrieved.
+
+```bash
+ssh-keygen -t rsa -C "$your_email"
+```
+
+Use the code below to show your public key.
+
+```bash
+cat ~/.ssh/id_rsa.pub
+```
+
+Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your user profile.
+Please copy the complete key starting with `ssh-` and ending with your username and host.
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
new file mode 100644
index 00000000000..76ca2a59911
--- /dev/null
+++ b/doc/system_hooks/system_hooks.md
@@ -0,0 +1,89 @@
+Your GitLab instance can perform HTTP POST requests on the following events: `create_project`, `delete_project`, `create_user`, `delete_user` and `change_team_member`.
+
+System hooks can be used, e.g. for logging or changing information in a LDAP server.
+
+#### Hooks request example:
+
+**Project created:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:54Z",
+ "event_name": "project_create",
+ "name": "StoreCloud",
+ "owner_email": "johnsmith@gmail.com",
+ "owner_name": "John Smith",
+ "path": "stormcloud",
+ "path_with_namespace": "jsmith/stormcloud",
+ "project_id": 74,
+}
+```
+
+**Project destroyed:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:58Z",
+ "event_name": "project_destroy",
+ "name": "Underscore",
+ "owner_email": "johnsmith@gmail.com",
+ "owner_name": "John Smith",
+ "path": "underscore",
+ "path_with_namespace": "jsmith/underscore",
+ "project_id": 73,
+}
+```
+
+**New Team Member:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:56Z",
+ "event_name": "user_add_to_team",
+ "project_access": "Master",
+ "project_id": 74,
+ "project_name": "StoreCloud",
+ "project_path": "storecloud",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
+}
+```
+
+**Team Member Removed:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:56Z",
+ "event_name": "user_remove_from_team",
+ "project_access": "Master",
+ "project_id": 74,
+ "project_name": "StoreCloud",
+ "project_path": "storecloud",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
+}
+```
+
+**User created:**
+
+```json
+{
+ "created_at": "2012-07-21T07:44:07Z",
+ "email": "js@gitlabhq.com",
+ "event_name": "user_create",
+ "name": "John Smith",
+ "user_id": 41
+}
+```
+
+**User removed:**
+
+```json
+{
+ "created_at": "2012-07-21T07:44:07Z",
+ "email": "js@gitlabhq.com",
+ "event_name": "user_destroy",
+ "name": "John Smith",
+ "user_id": 41
+}
+```
diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md
index 5bf8c367734..f8fb607e016 100644
--- a/doc/update/4.2-to-5.0.md
+++ b/doc/update/4.2-to-5.0.md
@@ -1,7 +1,7 @@
# From 4.2 to 5.0
## Warning
-GitLab 5.0 is affected by critical security vulnerability CVE-2013-4490. Please update to GitLab 5.4 immediately.
+GitLab 5.0 is affected by critical security vulnerability CVE-2013-4490.
## Important changes
diff --git a/doc/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md
index 24d96e43bad..ba56507dd81 100644
--- a/doc/update/5.0-to-5.1.md
+++ b/doc/update/5.0-to-5.1.md
@@ -1,7 +1,7 @@
# From 5.0 to 5.1
## Warning
-GitLab 5.1 is affected by critical security vulnerability CVE-2013-4490. Please [update to GitLab 5.4 immediately](5.1-to-5.4.md).
+GitLab 5.1 is affected by critical security vulnerability CVE-2013-4490.
## Release notes:
diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md
index e4eaee91b8e..466c815195f 100644
--- a/doc/update/5.1-to-5.2.md
+++ b/doc/update/5.1-to-5.2.md
@@ -1,7 +1,7 @@
# From 5.1 to 5.2
## Warning
-GitLab 5.2 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489. Please [update to GitLab 5.4 directly](5.1-to-5.4.md).
+GitLab 5.2 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
### 0. Backup
diff --git a/doc/update/5.1-to-5.4.md b/doc/update/5.1-to-5.4.md
index 39cacd381a3..56f4854daf0 100644
--- a/doc/update/5.1-to-5.4.md
+++ b/doc/update/5.1-to-5.4.md
@@ -1,9 +1,6 @@
# From 5.1 to 5.4
Also works starting from 5.2.
-## Notice
-Security vulnerabilities CVE-2013-4490 and CVE-2013-4489 have been patched in the latest version of GitLab 5.4.
-
### 0. Backup
It's useful to make a backup just in case things go south:
diff --git a/doc/update/5.1-to-6.0.md b/doc/update/5.1-to-6.0.md
index fa0f9ce54b6..5b74b1f893d 100644
--- a/doc/update/5.1-to-6.0.md
+++ b/doc/update/5.1-to-6.0.md
@@ -1,7 +1,7 @@
# From 5.1 to 6.0
## Warning
-GitLab 6.0 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489. Please [update to GitLab 6.2 immediately](6.0-to-6.2.md).
+GitLab 6.0 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
### Deprecations
diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md
index 7f89f6bf887..e3c3016db64 100644
--- a/doc/update/5.2-to-5.3.md
+++ b/doc/update/5.2-to-5.3.md
@@ -1,7 +1,7 @@
# From 5.2 to 5.3
## Warning
-GitLab 5.3 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489. Please [update to GitLab 5.4 directly](5.1-to-5.4.md).
+GitLab 5.3 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
### 0. Backup
diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md
index 7a24c11c223..213ce77ec38 100644
--- a/doc/update/5.3-to-5.4.md
+++ b/doc/update/5.3-to-5.4.md
@@ -1,8 +1,5 @@
# From 5.3 to 5.4
-## Notice
-Security vulnerabilities CVE-2013-4490 and CVE-2013-4489 have been patched in the latest version of GitLab 5.4.
-
### 0. Backup
It's useful to make a backup just in case things go south:
diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md
index 312e9a46080..c289fcb57fd 100644
--- a/doc/update/5.4-to-6.0.md
+++ b/doc/update/5.4-to-6.0.md
@@ -1,7 +1,7 @@
# From 5.4 to 6.0
## Warning
-GitLab 6.0 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489. Please [update to GitLab 6.2 immediately](6.0-to-6.2.md).
+GitLab 6.0 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
### Deprecations
diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md
index b7fd7634859..b4cc9203587 100644
--- a/doc/update/6.0-to-6.1.md
+++ b/doc/update/6.0-to-6.1.md
@@ -1,7 +1,7 @@
# From 6.0 to 6.1
## Warning
-GitLab 6.1 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489. Please [update to GitLab 6.2 directly](6.0-to-6.2.md).
+GitLab 6.1 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
# In 6.1 we remove a lot of deprecated code.
# You should update to 6.0 before installing 6.1 so all the necessary conversions are run.
diff --git a/doc/update/6.0-to-6.5.md b/doc/update/6.0-to-6.7.md
index e01cc589a83..5023e34f189 100644
--- a/doc/update/6.0-to-6.5.md
+++ b/doc/update/6.0-to-6.7.md
@@ -1,4 +1,4 @@
-# From 6.0 to 6.5
+# From 6.0 to 6.7
# In 6.1 we remove a lot of deprecated code.
# You should update to 6.0 before installing 6.1 or higher so all the necessary conversions are run.
@@ -28,8 +28,20 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```bash
cd /home/git/gitlab
sudo -u git -H git fetch --all
-sudo -u git -H git checkout 6-5-stable
-# For GitLab Enterprise Edition: sudo -u git -H git checkout 6-5-stable-ee
+```
+
+For Gitlab Community Edition:
+
+```bash
+sudo -u git -H git checkout 6-7-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 6-7-stable-ee
```
@@ -45,7 +57,7 @@ sudo apt-get install logrotate
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v1.8.0 # Addresses multiple critical security vulnerabilities
+sudo -u git -H git checkout v1.9.1 # Addresses multiple critical security vulnerabilities
```
### 5. Install libs, migrations, etc.
@@ -60,11 +72,14 @@ sudo -u git -H bundle install --without development test postgres --deployment
sudo -u git -H bundle install --without development test mysql --deployment
+# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Enable internal issue IDs (introduced in GitLab 6.1)
sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production
-sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
-sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
-sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 6. Update config files
@@ -72,11 +87,11 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
TIP: to see what changed in gitlab.yml.example in this release use next command:
```
-git diff 6-0-stable:config/gitlab.yml.example 6-5-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 6-7-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-5-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-5-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-7-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-7-stable/config/unicorn.rb.example but with your settings.
* Copy rack attack middleware config
```bash
@@ -125,3 +140,6 @@ Follow the [`upgrade guide from 5.4 to 6.0`](5.4-to-6.0.md), except for the data
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
+
+## Login issues after upgrade?
+If running in https mode, be sure to read [Can't Verify csrf token authenticity](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide#cant-verify-csrf-token-authenticitycant-get-past-login-pageredirected-to-login-page)
diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md
index 3c453a5ab42..c618e599dcb 100644
--- a/doc/update/6.1-to-6.2.md
+++ b/doc/update/6.1-to-6.2.md
@@ -1,8 +1,5 @@
# From 6.1 to 6.2
-## Notice
-Security vulnerabilities CVE-2013-4490 and CVE-2013-4489 have been patched in the latest version of GitLab 6.2.
-
# You should update to 6.1 before installing 6.2 so all the necessary conversions are run.
### 0. Backup
diff --git a/doc/update/6.5-to-6.6.md b/doc/update/6.5-to-6.6.md
new file mode 100644
index 00000000000..589c18c27c2
--- /dev/null
+++ b/doc/update/6.5-to-6.6.md
@@ -0,0 +1,94 @@
+# From 6.5 to 6.6
+
+### 0. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch --all
+```
+
+For Gitlab Community Edition:
+
+```bash
+sudo -u git -H git checkout 6-6-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 6-6-stable-ee
+```
+
+### 3. Update gitlab-shell (and its config)
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.8.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 6. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+## Things went south? Revert to previous version (6.5)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 6.4 to 6.5`](6.4-to-6.5.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/6.6-to-6.7.md b/doc/update/6.6-to-6.7.md
new file mode 100644
index 00000000000..8a16e5d67be
--- /dev/null
+++ b/doc/update/6.6-to-6.7.md
@@ -0,0 +1,98 @@
+# From 6.6 to 6.7
+
+### 0. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch --all
+```
+
+For Gitlab Community Edition:
+
+```bash
+sudo -u git -H git checkout 6-7-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 6-7-stable-ee
+```
+
+### 3. Update gitlab-shell (and its config)
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.9.1
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+# Update the logrotate configuration (keep logs for 90 days instead of 52 weeks)
+sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
+```
+
+
+### 5. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 6. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+## Things went south? Revert to previous version (6.6)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 6.5 to 6.6`](6.5-to-6.6.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/README.md b/doc/update/README.md
new file mode 100644
index 00000000000..97afd71bbe3
--- /dev/null
+++ b/doc/update/README.md
@@ -0,0 +1,5 @@
++ [The indivual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update)
++ [Uprader](upgrader.md)
++ [Ruby](ruby.md)
++ [Patch versions](patch_versions.md)
++ [MySQL to Postgress](mysql_to_postgress.md)
diff --git a/doc/update/mysql-to-postgresql.md b/doc/update/mysql-to-postgresql.md
new file mode 100644
index 00000000000..9a324545eb0
--- /dev/null
+++ b/doc/update/mysql-to-postgresql.md
@@ -0,0 +1,9 @@
+# Use the shell commands below to convert a MySQL GitLab database to a PostgreSQL one.
+
+```
+git clone https://github.com/lanyrd/mysql-postgresql-converter.git
+cd mysql-postgresql-converter
+mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production
+python db_converter.py databasename.mysql databasename.psql
+psql -f databasename.psql -d gitlabhq_production
+```
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index b284ff48365..2b947adaa13 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -18,10 +18,11 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```bash
cd /home/git/gitlab
-sudo -u git -H git pull origin STABLE_BRANCH
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout LATEST_TAG
```
-Replace STABLE_BRANCH with the minor version you want to upgrade to, for example `6-3-stable`.
+Replace LATEST_TAG with the latest GitLab tag you want to upgrade to, for example `v6.6.3`.
### 3. Update gitlab-shell if it is not the latest version
@@ -38,12 +39,12 @@ Replace LATEST_TAG with the latest GitLab Shell tag you want to upgrade to, for
```bash
cd /home/git/gitlab
-# MySQL
-sudo -u git -H bundle install --without development test postgres --deployment
-
#PostgreSQL
sudo -u git -H bundle install --without development test mysql --deployment
+# MySQL
+sudo -u git -H bundle install --without development test postgres --deployment
+
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
diff --git a/doc/update/ruby.md b/doc/update/ruby.md
index 3fc068c0ae2..9d0cafb3f05 100644
--- a/doc/update/ruby.md
+++ b/doc/update/ruby.md
@@ -41,7 +41,7 @@ Just to be sure we will reinstall the gems used by GitLab. Note that the `bundle
```bash
cd /home/git/gitlab
sudo -u git -H rm -rf vendor/bundle # remove existing Gem bundle
-sudo -u git -H bundle install --deployment --without development test postgres aws # Assuming MySQL
+sudo -u git -H bundle install --deployment --without development test mysql aws # Assuming PostgreSQL
```
### 6. Start GitLab
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
index 99c81a7877c..305ef961be5 100644
--- a/doc/update/upgrader.md
+++ b/doc/update/upgrader.md
@@ -28,3 +28,15 @@ __GitLab Upgrader is available only for GitLab version 6.4.2 or higher__
sudo service gitlab start
sudo service nginx restart
+
+### 4. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
new file mode 100644
index 00000000000..f80891e264d
--- /dev/null
+++ b/doc/web_hooks/web_hooks.md
@@ -0,0 +1,114 @@
+Project web hooks allow you to trigger an URL if new code is pushed or a new issue is created.
+
+---
+
+You can configure web hook to listen for specific events like pushes, issues, merge requests.
+GitLab will send POST request with data to web hook URL.
+Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
+If you send a web hook to an SSL endpoint [the certificate will not be verified](https://gitlab.com/gitlab-org/gitlab-ce/blob/ccd617e58ea71c42b6b073e692447d0fe3c00be6/app/models/web_hook.rb#L35) since many people use self-signed certificates.
+
+---
+
+#### Push events
+
+Triggered when you push to the repository except pushing tags.
+
+**Request body:**
+
+```json
+{
+ "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
+ "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "ref": "refs/heads/master",
+ "user_id": 4,
+ "user_name": "John Smith",
+ "project_id": 15,
+ "repository": {
+ "name": "Diaspora",
+ "url": "git@localhost:diaspora.git",
+ "description": "",
+ "homepage": "http://localhost/diaspora"
+ },
+ "commits": [
+ {
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "message": "Update Catalan translation to e38cb41.",
+ "timestamp": "2011-12-12T14:27:31+02:00",
+ "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "author": {
+ "name": "Jordi Mallach",
+ "email": "jordi@softcatala.org"
+ }
+ },
+ {
+ "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "message": "fixed readme",
+ "timestamp": "2012-01-03T23:36:29+02:00",
+ "url": "http://localhost/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "author": {
+ "name": "GitLab dev user",
+ "email": "gitlabdev@dv6700.(none)"
+ }
+ }
+ ],
+ "total_commits_count": 4
+}
+```
+
+#### Issues events
+
+Triggered when a new issue is created or an existing issue was updated/closed/reopened.
+
+**Request body:**
+
+```json
+{
+ "object_kind": "issue",
+ "object_attributes": {
+ "id": 301,
+ "title": "New API: create/update/delete file",
+ "assignee_id": 51,
+ "author_id": 51,
+ "project_id": 14,
+ "created_at": "2013-12-03T17:15:43Z",
+ "updated_at": "2013-12-03T17:15:43Z",
+ "position": 0,
+ "branch_name": null,
+ "description": "Create new API for manipulations with repository",
+ "milestone_id": null,
+ "state": "opened",
+ "iid": 23
+ }
+}
+```
+
+#### Merge request events
+
+Triggered when a new merge request is created or an existing merge request was updated/merges/closed.
+
+**Request body:**
+
+```json
+{
+ "object_kind": "merge_request",
+ "object_attributes": {
+ "id": 99,
+ "target_branch": "master",
+ "source_branch": "ms-viewport",
+ "source_project_id": 14,
+ "author_id": 51,
+ "assignee_id": 6,
+ "title": "MS-Viewport",
+ "created_at": "2013-12-03T17:23:34Z",
+ "updated_at": "2013-12-03T17:23:34Z",
+ "st_commits": null,
+ "st_diffs": null,
+ "milestone_id": null,
+ "state": "opened",
+ "merge_status": "unchecked",
+ "target_project_id": 14,
+ "iid": 1,
+ "description": ""
+ }
+}
+```
diff --git a/doc/workflow/workflow.md b/doc/workflow/workflow.md
new file mode 100644
index 00000000000..1c238f74957
--- /dev/null
+++ b/doc/workflow/workflow.md
@@ -0,0 +1,26 @@
+1. Clone project
+
+ ```bash
+ git clone git@example.com:project-name.git
+ ```
+2. Create branch with your feature
+
+ ```bash
+ git checkout -b $feature_name
+ ```
+
+3. Write code. Comit changes
+
+ ```bash
+ git commit -am "My feature is ready"
+ ```
+
+4. Push your branch to GitLab
+
+ ```bash
+ git push origin $feature_name
+ ```
+
+5. Review your code on Commits page
+6. Create a merge request
+7. Your team lead will review the code &amp; merge it to the main branch
diff --git a/features/admin/users.feature b/features/admin/users.feature
index 7f503cf9235..ce9f32f50d9 100644
--- a/features/admin/users.feature
+++ b/features/admin/users.feature
@@ -14,3 +14,9 @@ Feature: Admin Users
And Click save
Then See username error message
And Not changed form action url
+
+ Scenario: Edit my user attributes
+ Given I visit admin users page
+ And click edit on my user
+ When I submit modified user
+ Then I see user attributes changed
diff --git a/features/group.feature b/features/group.feature
index aa49056fee2..4e11bcba939 100644
--- a/features/group.feature
+++ b/features/group.feature
@@ -74,7 +74,7 @@ Feature: Groups
When I visit group "Owned" members page
Then I should see user "John Doe" in team list
Then I should see user "Mary Jane" in team list
- Then I should not see the "Remove User From Group" button for "Mary Jane"
+ Then I should not see the "Remove User From Group" button for "John Doe"
@javascript
Scenario: Guest should be able to remove himself from group
@@ -97,7 +97,6 @@ Feature: Groups
# Remove others
- @javascript
Scenario: Owner should be able to remove other users from group
Given "Mary Jane" is owner of group "Owned"
When I visit group "Owned" members page
diff --git a/features/profile/emails.feature b/features/profile/emails.feature
new file mode 100644
index 00000000000..148fc766081
--- /dev/null
+++ b/features/profile/emails.feature
@@ -0,0 +1,25 @@
+Feature: Profile Emails
+ Background:
+ Given I sign in as a user
+ And I visit profile emails page
+
+ Scenario: I should see emails
+ Then I should see my emails
+
+ Scenario: Add new email
+ Given I submit new email "my@email.com"
+ Then I should see new email "my@email.com"
+ And I should see my emails
+
+ Scenario: Add duplicate email
+ Given I submit duplicate email @user.email
+ Then I should not have @user.email added
+ And I should see my emails
+
+ Scenario: Remove email
+ Given I submit new email "my@email.com"
+ Then I should see new email "my@email.com"
+ And I should see my emails
+ Then I click link "Remove" for "my@email.com"
+ Then I should not see email "my@email.com"
+ And I should see my emails
diff --git a/features/project/archived_projects.feature b/features/project/archived.feature
index 9aac29384ba..9aac29384ba 100644
--- a/features/project/archived_projects.feature
+++ b/features/project/archived.feature
diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature
index 4fa4dc26a1b..fcf8b7694f4 100644
--- a/features/project/commits/branches.feature
+++ b/features/project/commits/branches.feature
@@ -16,11 +16,7 @@ Feature: Project Browse branches
Given I click link "Protected"
Then I should see "Shop" protected branches list
- # @wip
- # Scenario: I can download project by branch
-
- # @wip
- # Scenario: I can view protected branches
-
- # @wip
- # Scenario: I can manage protected branches
+ Scenario: I create a branch
+ Given I click new branch link
+ When I submit new branch form
+ Then I should see new branch created
diff --git a/features/project/commits/commit_comments.feature b/features/project/commits/comments.feature
index a1aa745a681..a1aa745a681 100644
--- a/features/project/commits/commit_comments.feature
+++ b/features/project/commits/comments.feature
diff --git a/features/project/commits/commit_diff_comments.feature b/features/project/commits/diff_comments.feature
index b26019f832f..b26019f832f 100644
--- a/features/project/commits/commit_diff_comments.feature
+++ b/features/project/commits/diff_comments.feature
diff --git a/features/project/commits/user_lookup.feature b/features/project/commits/user_lookup.feature
new file mode 100644
index 00000000000..f3864c0ab38
--- /dev/null
+++ b/features/project/commits/user_lookup.feature
@@ -0,0 +1,14 @@
+Feature: Project Browse Commits User Lookup
+ Background:
+ Given I sign in as a user
+ And I own a project
+ And I have the user that authored the commits
+ And I visit my project's commits page
+
+ Scenario: I browse commit from list
+ Given I click on commit link
+ Then I see commit info
+
+ Scenario: I browse another commit from list
+ Given I click on another commit link
+ Then I see other commit info \ No newline at end of file
diff --git a/features/project/create_project.feature b/features/project/create.feature
index 395a3218b2b..bb8e3a368ed 100644
--- a/features/project/create_project.feature
+++ b/features/project/create.feature
@@ -3,6 +3,7 @@ Feature: Create Project
A user with ability to create a project
Should be able to create a new one
+ @javascript
Scenario: User create a project
Given I sign in as a user
When I visit new project page
@@ -19,4 +20,4 @@ Feature: Create Project
And I click on HTTP
Then Remote url should update to http link
And If I click on SSH
- Then Remote url should update to ssh link \ No newline at end of file
+ Then Remote url should update to ssh link
diff --git a/features/project/fork_project.feature b/features/project/fork.feature
index dc477ca3bf3..dc477ca3bf3 100644
--- a/features/project/fork_project.feature
+++ b/features/project/fork.feature
diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature
index 966905645a2..2d94b98c90b 100644
--- a/features/project/forked_merge_requests.feature
+++ b/features/project/forked_merge_requests.feature
@@ -32,3 +32,9 @@ Feature: Project Forked Merge Requests
And I fill out an invalid "Merge Request On Forked Project" merge request
And I submit the merge request
Then I should see validation errors
+
+ @javascript
+ Scenario: Merge request should target fork repository by default
+ Given I visit project "Forked Shop" merge requests page
+ And I click link "New Merge Request"
+ Then the target repository should be the original repository \ No newline at end of file
diff --git a/features/project/issues/filter_labels.feature b/features/project/issues/filter_labels.feature
new file mode 100644
index 00000000000..8df7a119e84
--- /dev/null
+++ b/features/project/issues/filter_labels.feature
@@ -0,0 +1,26 @@
+Feature: Project Filter Labels
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And project "Shop" has issue "Bugfix1" with tags: "bug", "feature"
+ And project "Shop" has issue "Bugfix2" with tags: "bug", "enhancement"
+ And project "Shop" has issue "Feature1" with tags: "feature"
+ Given I visit project "Shop" issues page
+
+ Scenario: I should see project issues
+ Then I should see "bug" in labels filter
+ And I should see "feature" in labels filter
+ And I should see "enhancement" in labels filter
+
+ Scenario: I filter by one label
+ Given I click link "bug"
+ Then I should see "Bugfix1" in issues list
+ And I should see "Bugfix2" in issues list
+ And I should not see "Feature1" in issues list
+
+ Scenario: I filter by two labels
+ Given I click link "bug"
+ And I click link "feature"
+ Then I should see "Bugfix1" in issues list
+ And I should not see "Bugfix2" in issues list
+ And I should not see "Feature1" in issues list
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 3ca099b9572..6a2ba7e3b28 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -29,7 +29,13 @@ Feature: Project Merge Requests
And I click link "Close"
Then I should see closed merge request "Bug NS-04"
- @javascript
+ Scenario: I reopen merge request page
+ Given I click link "Bug NS-04"
+ And I click link "Close"
+ Then I should see closed merge request "Bug NS-04"
+ When I click link "Reopen"
+ Then I should see reopened merge request "Bug NS-04"
+
Scenario: I submit new unassigned merge request
Given I click link "New Merge Request"
And I submit new merge request "Wiki Feature"
@@ -89,3 +95,47 @@ Feature: Project Merge Requests
Given I visit merge request page "Bug NS-04"
And I leave a comment with a header containing "Comment with a header"
Then The comment with the header should not have an ID
+
+ # Toggling inline comments
+
+ @javascript
+ Scenario: I hide comments on a merge request diff with comments in a single file
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I switch to the diff tab
+ And I leave a comment like "Line is wrong" on line 39 of the second file
+ And I click link "Hide inline discussion" of the second file
+ Then I should not see a comment like "Line is wrong" in the second file
+
+ @javascript
+ Scenario: I show comments on a merge request diff with comments in a single file
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I switch to the diff tab
+ And I leave a comment like "Line is wrong" on line 39 of the second file
+ And I click link "Hide inline discussion" of the second file
+ And I click link "Show inline discussion" of the second file
+ Then I should see a comment like "Line is wrong" in the second file
+
+ @javascript
+ Scenario: I hide comments on a merge request diff with comments in multiple files
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I switch to the diff tab
+ And I leave a comment like "Line is correct" on line 12 of the first file
+ And I leave a comment like "Line is wrong" on line 39 of the second file
+ And I click link "Hide inline discussion" of the second file
+ Then I should not see a comment like "Line is wrong" in the second file
+ And I should still see a comment like "Line is correct" in the first file
+
+ @javascript
+ Scenario: I show comments on a merge request diff with comments in multiple files
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I switch to the diff tab
+ And I leave a comment like "Line is correct" on line 12 of the first file
+ And I leave a comment like "Line is wrong" on line 39 of the second file
+ And I click link "Hide inline discussion" of the second file
+ And I click link "Show inline discussion" of the second file
+ Then I should see a comment like "Line is wrong" in the second file
+ And I should still see a comment like "Line is correct" in the first file
diff --git a/features/project/public_projects.feature b/features/project/public.feature
index c5a9da14c54..c5a9da14c54 100644
--- a/features/project/public_projects.feature
+++ b/features/project/public.feature
diff --git a/features/project/service.feature b/features/project/service.feature
index 46b983e8f9a..a5af065c9e7 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -37,6 +37,12 @@ Feature: Project Services
And I fill Assembla settings
Then I should see Assembla service settings saved
+ Scenario: Activate Slack service
+ When I visit project "Shop" services page
+ And I click Slack service link
+ And I fill Slack settings
+ Then I should see Slack service settings saved
+
Scenario: Activate email on push service
When I visit project "Shop" services page
And I click email on push service link
diff --git a/features/project/source/markdown_render.feature b/features/project/source/markdown_render.feature
index 42d6ebcb1cf..970a9e57864 100644
--- a/features/project/source/markdown_render.feature
+++ b/features/project/source/markdown_render.feature
@@ -4,9 +4,7 @@ Feature: Project markdown render
And I own project "Delta"
Given I visit project source page
- # -------------------------------------------
- # README
- # -------------------------------------------
+ # Tree README
Scenario: Tree view should have correct links in README
Given I go directory which contains README file
@@ -41,9 +39,7 @@ Feature: Project markdown render
Then I should see rendered README which contains correct links
And Header "Application details" should have correct id and link
- # -------------------------------------------
- # File content
- # -------------------------------------------
+ # Blob
Scenario: I navigate to doc directory to view documentation in master
And I navigate to the doc/api/README
@@ -61,9 +57,7 @@ Feature: Project markdown render
And I navigate to the doc/api/README
And Header "GitLab API" should have correct id and link
- # -------------------------------------------
- # Markdown branch README
- # -------------------------------------------
+ # Markdown branch
Scenario: I browse files from markdown branch
When I visit markdown branch
@@ -93,9 +87,31 @@ Feature: Project markdown render
And I click on raketasks in doc/api/README
Then I should see correct directory rendered for markdown branch
- # -------------------------------------------
+ Scenario: Tree markdown links view empty urls should have correct urls
+ When I visit markdown branch
+ Then The link with text "empty" should have url "tree/markdown"
+ When I visit markdown branch "README.md" blob
+ Then The link with text "empty" should have url "blob/markdown/README.md"
+ When I visit markdown branch "d" tree
+ Then The link with text "empty" should have url "tree/markdown/d"
+ When I visit markdown branch "d/README.md" blob
+ Then The link with text "empty" should have url "blob/markdown/d/README.md"
+
+ # "ID" means "#id" on the tests below, because we are unable to escape the hash sign.
+ # which Spinach interprets as the start of a comment.
+ Scenario: All markdown links with ids should have correct urls
+ When I visit markdown branch
+ Then The link with text "ID" should have url "tree/markdownID"
+ Then The link with text "/ID" should have url "tree/markdownID"
+ Then The link with text "README.mdID" should have url "blob/markdown/README.mdID"
+ Then The link with text "d/README.mdID" should have url "blob/markdown/d/README.mdID"
+ When I visit markdown branch "README.md" blob
+ Then The link with text "ID" should have url "blob/markdown/README.mdID"
+ Then The link with text "/ID" should have url "blob/markdown/README.mdID"
+ Then The link with text "README.mdID" should have url "blob/markdown/README.mdID"
+ Then The link with text "d/README.mdID" should have url "blob/markdown/d/README.mdID"
+
# Wiki
- # -------------------------------------------
Scenario: I create a wiki page with different links
Given I go to wiki page
diff --git a/features/public/public_projects.feature b/features/public/projects.feature
index 57fe834b4bf..57fe834b4bf 100644
--- a/features/public/public_projects.feature
+++ b/features/public/projects.feature
diff --git a/features/public/public_groups.feature b/features/public/public_groups.feature
new file mode 100644
index 00000000000..7f1ec718e35
--- /dev/null
+++ b/features/public/public_groups.feature
@@ -0,0 +1,118 @@
+Feature: Public Projects Feature
+ Background:
+ Given group "TestGroup" has private project "Enterprise"
+
+ Scenario: I should not see group with private projects as visitor
+ When I visit group "TestGroup" page
+ Then I should be redirected to sign in page
+
+ Scenario: I should not see group with private projects group as user
+ When I sign in as a user
+ And I visit group "TestGroup" page
+ Then page status code should be 404
+
+ Scenario: I should not see group with private and internal projects as visitor
+ Given group "TestGroup" has internal project "Internal"
+ When I visit group "TestGroup" page
+ Then I should be redirected to sign in page
+
+ Scenario: I should see group with private and internal projects as user
+ Given group "TestGroup" has internal project "Internal"
+ When I sign in as a user
+ And I visit group "TestGroup" page
+ Then I should see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group issues for internal project as user
+ 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
+
+ Scenario: I should see group merge requests for internal project as user
+ 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
+
+ Scenario: I should see group's members as user
+ Given group "TestGroup" has internal project "Internal"
+ And "John Doe" is owner of group "TestGroup"
+ When I sign in as a user
+ And I visit group "TestGroup" members page
+ Then I should see group member "John Doe"
+ And I should not see member roles
+
+ Scenario: I should see group with private, internal and public projects as visitor
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ When I visit group "TestGroup" page
+ Then I should see project "Community" items
+ And I should not see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group issues for public project as visitor
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ When I visit group "TestGroup" issues page
+ Then I should see project "Community" items
+ And I should not see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group merge requests for public project as visitor
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ When I visit group "TestGroup" merge requests page
+ Then I should see project "Community" items
+ And I should not see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group's members as visitor
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ And "John Doe" is owner of group "TestGroup"
+ When I visit group "TestGroup" members page
+ Then I should see group member "John Doe"
+ And I should not see member roles
+
+ Scenario: I should see group with private, internal and public projects as user
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ When I sign in as a user
+ And I visit group "TestGroup" page
+ Then I should see project "Community" items
+ And I should see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group issues for internal and public projects as user
+ Given group "TestGroup" has internal project "Internal"
+ 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
+
+ Scenario: I should see group merge requests for internal and public projects as user
+ Given group "TestGroup" has internal project "Internal"
+ 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
+
+ Scenario: I should see group's members as user
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ And "John Doe" is owner of group "TestGroup"
+ When I sign in as a user
+ And I visit group "TestGroup" members page
+ Then I should see group member "John Doe"
+ And I should not see member roles
diff --git a/features/snippets/discover_snippets.feature b/features/snippets/discover.feature
index d6fd2cd7808..d6fd2cd7808 100644
--- a/features/snippets/discover_snippets.feature
+++ b/features/snippets/discover.feature
diff --git a/features/snippets/user_snippets.feature b/features/snippets/user.feature
index 4c8a91501c4..4c8a91501c4 100644
--- a/features/snippets/user_snippets.feature
+++ b/features/snippets/user.feature
diff --git a/features/steps/admin/admin_active_tab.rb b/features/steps/admin/active_tab.rb
index ccafe09c18f..ccafe09c18f 100644
--- a/features/steps/admin/admin_active_tab.rb
+++ b/features/steps/admin/active_tab.rb
diff --git a/features/steps/admin/admin_broadcast_messages.rb b/features/steps/admin/broadcast_messages.rb
index a35fa34a3a2..a35fa34a3a2 100644
--- a/features/steps/admin/admin_broadcast_messages.rb
+++ b/features/steps/admin/broadcast_messages.rb
diff --git a/features/steps/admin/admin_groups.rb b/features/steps/admin/groups.rb
index 9c1bcfefb9c..9c1bcfefb9c 100644
--- a/features/steps/admin/admin_groups.rb
+++ b/features/steps/admin/groups.rb
diff --git a/features/steps/admin/admin_logs.rb b/features/steps/admin/logs.rb
index 83958545c4d..83958545c4d 100644
--- a/features/steps/admin/admin_logs.rb
+++ b/features/steps/admin/logs.rb
diff --git a/features/steps/admin/admin_projects.rb b/features/steps/admin/projects.rb
index b410b23851b..b410b23851b 100644
--- a/features/steps/admin/admin_projects.rb
+++ b/features/steps/admin/projects.rb
diff --git a/features/steps/admin/admin_users.rb b/features/steps/admin/users.rb
index 33c1344eaeb..659008dd875 100644
--- a/features/steps/admin/admin_users.rb
+++ b/features/steps/admin/users.rb
@@ -31,4 +31,17 @@ class AdminUsers < Spinach::FeatureSteps
And 'Not changed form action url' do
page.should have_selector %(form[action="/admin/users/#{@user.username}"])
end
+
+ step 'I submit modified user' do
+ check :user_can_create_group
+ click_button 'Save'
+ end
+
+ step 'I see user attributes changed' do
+ page.should have_content 'Can create groups: Yes'
+ end
+
+ step 'click edit on my user' do
+ find("#edit_user_#{current_user.id}").click
+ end
end
diff --git a/features/steps/dashboard/dashboard_active_tab.rb b/features/steps/dashboard/active_tab.rb
index 8f5f0eed816..8f5f0eed816 100644
--- a/features/steps/dashboard/dashboard_active_tab.rb
+++ b/features/steps/dashboard/active_tab.rb
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 3526006c94a..394acd3fe8f 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -25,7 +25,7 @@ class Dashboard < Spinach::FeatureSteps
find("#merge_request_target_project_id").value.should == @project.id.to_s
find("#merge_request_source_branch").value.should == "new_design"
find("#merge_request_target_branch").value.should == "master"
- find("#merge_request_title").value.should == "New Design"
+ find("#merge_request_title").value.should == "New design"
end
Given 'user with name "John Doe" joined project "Shop"' do
diff --git a/features/steps/dashboard/dashboard_event_filters.rb b/features/steps/dashboard/event_filters.rb
index d0fe5c9b64b..d0fe5c9b64b 100644
--- a/features/steps/dashboard/dashboard_event_filters.rb
+++ b/features/steps/dashboard/event_filters.rb
diff --git a/features/steps/dashboard/dashboard_issues.rb b/features/steps/dashboard/issues.rb
index 1344edfa80b..1344edfa80b 100644
--- a/features/steps/dashboard/dashboard_issues.rb
+++ b/features/steps/dashboard/issues.rb
diff --git a/features/steps/dashboard/dashboard_merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index 62d84506c49..62d84506c49 100644
--- a/features/steps/dashboard/dashboard_merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
diff --git a/features/steps/dashboard/dashboard_projects.rb b/features/steps/dashboard/projects.rb
index 85251565446..85251565446 100644
--- a/features/steps/dashboard/dashboard_projects.rb
+++ b/features/steps/dashboard/projects.rb
diff --git a/features/steps/dashboard/dashboard_search.rb b/features/steps/dashboard/search.rb
index 32966a8617a..32966a8617a 100644
--- a/features/steps/dashboard/dashboard_search.rb
+++ b/features/steps/dashboard/search.rb
diff --git a/features/steps/dashboard/dashboard_with_archived_projects.rb b/features/steps/dashboard/with_archived_projects.rb
index 1bc69555b56..1bc69555b56 100644
--- a/features/steps/dashboard/dashboard_with_archived_projects.rb
+++ b/features/steps/dashboard/with_archived_projects.rb
diff --git a/features/steps/group/group.rb b/features/steps/group/group.rb
index bd59b7a12f6..81472d1ca35 100644
--- a/features/steps/group/group.rb
+++ b/features/steps/group/group.rb
@@ -29,6 +29,7 @@ class Groups < Spinach::FeatureSteps
And 'I select user "Mary Jane" from list with role "Reporter"' do
user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane")
+ click_link 'Add members'
within ".users-group-form" do
select2(user.id, from: "#user_ids", multiple: true)
select "Reporter", from: "group_access"
diff --git a/features/steps/profile/profile_active_tab.rb b/features/steps/profile/active_tab.rb
index ee9f5f201cf..ee9f5f201cf 100644
--- a/features/steps/profile/profile_active_tab.rb
+++ b/features/steps/profile/active_tab.rb
diff --git a/features/steps/profile/emails.rb b/features/steps/profile/emails.rb
new file mode 100644
index 00000000000..99588c85991
--- /dev/null
+++ b/features/steps/profile/emails.rb
@@ -0,0 +1,48 @@
+class ProfileEmails < Spinach::FeatureSteps
+ include SharedAuthentication
+
+ Then 'I visit profile emails page' do
+ visit profile_emails_path
+ end
+
+ Then 'I should see my emails' do
+ page.should have_content(@user.email)
+ @user.emails.each do |email|
+ page.should have_content(email.email)
+ end
+ end
+
+ And 'I submit new email "my@email.com"' do
+ fill_in "email_email", with: "my@email.com"
+ click_button "Add"
+ end
+
+ Then 'I should see new email "my@email.com"' do
+ email = @user.emails.find_by(email: "my@email.com")
+ email.should_not be_nil
+ page.should have_content("my@email.com")
+ end
+
+ Then 'I should not see email "my@email.com"' do
+ email = @user.emails.find_by(email: "my@email.com")
+ email.should be_nil
+ page.should_not have_content("my@email.com")
+ end
+
+ Then 'I click link "Remove" for "my@email.com"' do
+ # there should only be one remove button at this time
+ click_link "Remove"
+ # force these to reload as they have been cached
+ @user.emails.reload
+ end
+
+ And 'I submit duplicate email @user.email' do
+ fill_in "email_email", with: @user.email
+ click_button "Add"
+ end
+
+ Then 'I should not have @user.email added' do
+ email = @user.emails.find_by(email: @user.email)
+ email.should be_nil
+ end
+end
diff --git a/features/steps/profile/profile_notifications.rb b/features/steps/profile/notifications.rb
index 7a41687dfde..e884df3098e 100644
--- a/features/steps/profile/profile_notifications.rb
+++ b/features/steps/profile/notifications.rb
@@ -8,6 +8,5 @@ class ProfileNotifications < Spinach::FeatureSteps
step 'I should see global notifications settings' do
page.should have_content "Notifications settings"
- page.should have_content "Global setting"
end
end
diff --git a/features/steps/profile/profile_ssh_keys.rb b/features/steps/profile/ssh_keys.rb
index 65ca824bb5b..65ca824bb5b 100644
--- a/features/steps/profile/profile_ssh_keys.rb
+++ b/features/steps/profile/ssh_keys.rb
diff --git a/features/steps/project/project_active_tab.rb b/features/steps/project/active_tab.rb
index dcc252f4765..dcc252f4765 100644
--- a/features/steps/project/project_active_tab.rb
+++ b/features/steps/project/active_tab.rb
diff --git a/features/steps/project/project_archived.rb b/features/steps/project/archived.rb
index dfbe762c438..dfbe762c438 100644
--- a/features/steps/project/project_archived.rb
+++ b/features/steps/project/archived.rb
diff --git a/features/steps/project/browse_branches.rb b/features/steps/project/browse_branches.rb
new file mode 100644
index 00000000000..30c8cef80c8
--- /dev/null
+++ b/features/steps/project/browse_branches.rb
@@ -0,0 +1,51 @@
+class ProjectBrowseBranches < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ step 'I should see "Shop" recent branches list' do
+ page.should have_content "Branches"
+ page.should have_content "master"
+ end
+
+ step 'I click link "All"' do
+ click_link "All"
+ end
+
+ step 'I should see "Shop" all branches list' do
+ page.should have_content "Branches"
+ page.should have_content "master"
+ end
+
+ step 'I click link "Protected"' do
+ click_link "Protected"
+ end
+
+ step 'I should see "Shop" protected branches list' do
+ within ".protected-branches-list" do
+ page.should have_content "stable"
+ page.should_not have_content "master"
+ end
+ end
+
+ step 'project "Shop" has protected branches' do
+ project = Project.find_by(name: "Shop")
+ project.protected_branches.create(name: "stable")
+ end
+
+ step 'I click new branch link' do
+ click_link "New branch"
+ end
+
+ step 'I submit new branch form' do
+ fill_in 'branch_name', with: 'deploy_keys'
+ fill_in 'ref', with: 'master'
+ click_button 'Create branch'
+ end
+
+ step 'I should see new branch created' do
+ within '.all-branches' do
+ page.should have_content 'deploy_keys'
+ end
+ end
+end
diff --git a/features/steps/project/project_browse_commits.rb b/features/steps/project/browse_commits.rb
index d667b58240f..d667b58240f 100644
--- a/features/steps/project/project_browse_commits.rb
+++ b/features/steps/project/browse_commits.rb
diff --git a/features/steps/project/browse_commits_user_lookup.rb b/features/steps/project/browse_commits_user_lookup.rb
new file mode 100644
index 00000000000..328be373553
--- /dev/null
+++ b/features/steps/project/browse_commits_user_lookup.rb
@@ -0,0 +1,35 @@
+class ProjectBrowseCommitsUserLookup < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ Given 'I have the user that authored the commits' do
+ @user = create(:user, email: 'dmitriy.zaporozhets@gmail.com')
+ create(:email, { user: @user, email: 'dzaporozhets@sphereconsultinginc.com' })
+ end
+
+ Given 'I click on commit link' do
+ visit project_commit_path(@project, ValidCommit::ID)
+ end
+
+ Given 'I click on another commit link' do
+ visit project_commit_path(@project, ValidCommitWithAltEmail::ID)
+ end
+
+ Then 'I see commit info' do
+ page.should have_content ValidCommit::MESSAGE
+ check_author_link(ValidCommit::AUTHOR_EMAIL)
+ end
+
+ Then 'I see other commit info' do
+ page.should have_content ValidCommitWithAltEmail::MESSAGE
+ check_author_link(ValidCommitWithAltEmail::AUTHOR_EMAIL)
+ end
+
+ def check_author_link(email)
+ author_link = find('.commit-author-link')
+ author_link['href'].should == user_path(@user)
+ author_link['data-original-title'].should == email
+ find('.commit-author-name').text.should == @user.name
+ end
+end
diff --git a/features/steps/project/project_browse_files.rb b/features/steps/project/browse_files.rb
index 069086d5eac..069086d5eac 100644
--- a/features/steps/project/project_browse_files.rb
+++ b/features/steps/project/browse_files.rb
diff --git a/features/steps/project/project_browse_git_repo.rb b/features/steps/project/browse_git_repo.rb
index cd9a60f49cb..cd9a60f49cb 100644
--- a/features/steps/project/project_browse_git_repo.rb
+++ b/features/steps/project/browse_git_repo.rb
diff --git a/features/steps/project/project_browse_tags.rb b/features/steps/project/browse_tags.rb
index 0cbfa0d80aa..0cbfa0d80aa 100644
--- a/features/steps/project/project_browse_tags.rb
+++ b/features/steps/project/browse_tags.rb
diff --git a/features/steps/project/create_project.rb b/features/steps/project/create.rb
index b59345e7078..b42e5bd3623 100644
--- a/features/steps/project/create_project.rb
+++ b/features/steps/project/create.rb
@@ -8,8 +8,8 @@ class CreateProject < Spinach::FeatureSteps
end
Then 'I should see project page' do
- current_path.should == project_path(Project.last)
page.should have_content "Empty"
+ current_path.should == project_path(Project.last)
end
And 'I should see empty project instuctions' do
diff --git a/features/steps/project/filter_labels.rb b/features/steps/project/filter_labels.rb
new file mode 100644
index 00000000000..5926d69d6c7
--- /dev/null
+++ b/features/steps/project/filter_labels.rb
@@ -0,0 +1,70 @@
+class ProjectFilterLabels < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ Then 'I should see "bug" in labels filter' do
+ within ".labels-filter" do
+ page.should have_content "bug"
+ end
+ end
+
+ And 'I should see "feature" in labels filter' do
+ within ".labels-filter" do
+ page.should have_content "feature"
+ end
+ end
+
+ And 'I should see "enhancement" in labels filter' do
+ within ".labels-filter" do
+ page.should have_content "enhancement"
+ end
+ end
+
+ Then 'I should see "Bugfix1" in issues list' do
+ within ".issues-list" do
+ page.should have_content "Bugfix1"
+ end
+ end
+
+ And 'I should see "Bugfix2" in issues list' do
+ within ".issues-list" do
+ page.should have_content "Bugfix2"
+ end
+ end
+
+ And 'I should not see "Bugfix2" in issues list' do
+ within ".issues-list" do
+ page.should_not have_content "Bugfix2"
+ end
+ end
+
+ And 'I should not see "Feature1" in issues list' do
+ within ".issues-list" do
+ page.should_not have_content "Feature1"
+ end
+ end
+
+ Given 'I click link "bug"' do
+ click_link "bug"
+ end
+
+ Given 'I click link "feature"' do
+ click_link "feature"
+ end
+
+ And 'project "Shop" has issue "Bugfix1" with tags: "bug", "feature"' do
+ project = Project.find_by(name: "Shop")
+ create(:issue, title: "Bugfix1", project: project, label_list: ['bug', 'feature'])
+ end
+
+ And 'project "Shop" has issue "Bugfix2" with tags: "bug", "enhancement"' do
+ project = Project.find_by(name: "Shop")
+ create(:issue, title: "Bugfix2", project: project, label_list: ['bug', 'enhancement'])
+ end
+
+ And 'project "Shop" has issue "Feature1" with tags: "feature"' do
+ project = Project.find_by(name: "Shop")
+ create(:issue, title: "Feature1", project: project, label_list: 'feature')
+ end
+end
diff --git a/features/steps/project/project_fork.rb b/features/steps/project/fork.rb
index c00d9014b1d..c00d9014b1d 100644
--- a/features/steps/project/project_fork.rb
+++ b/features/steps/project/fork.rb
diff --git a/features/steps/project/project_forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 4cc99f8af55..df69cb75437 100644
--- a/features/steps/project/project_forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -159,8 +159,11 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
#If this isn't filled in the rest of the validations won't be triggered
fill_in "merge_request_title", with: "Merge Request On Forked Project"
+
+ select "Select branch", from: "merge_request_target_branch"
+
find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
- find(:select, "merge_request_target_project_id", {}).value.should == @forked_project.id.to_s
+ find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s
find(:select, "merge_request_source_branch", {}).value.should == ""
find(:select, "merge_request_target_branch", {}).value.should == ""
end
@@ -168,7 +171,10 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
step 'I should see validation errors' do
page.should have_content "Source branch can't be blank"
page.should have_content "Target branch can't be blank"
- page.should have_content "Branch conflict You can not use same project/branch for source and target"
+ end
+
+ step 'the target repository should be the original repository' do
+ page.should have_select("merge_request_target_project_id", selected: project.path_with_namespace)
end
def project
diff --git a/features/steps/project/project_graph.rb b/features/steps/project/graph.rb
index 89fe5fdeadf..89fe5fdeadf 100644
--- a/features/steps/project/project_graph.rb
+++ b/features/steps/project/graph.rb
diff --git a/features/steps/project/project_hooks.rb b/features/steps/project/hooks.rb
index 19ff3244543..19ff3244543 100644
--- a/features/steps/project/project_hooks.rb
+++ b/features/steps/project/hooks.rb
diff --git a/features/steps/project/project_issue_tracker.rb b/features/steps/project/issue_tracker.rb
index c2fd4e15c9e..c2fd4e15c9e 100644
--- a/features/steps/project/project_issue_tracker.rb
+++ b/features/steps/project/issue_tracker.rb
diff --git a/features/steps/project/project_issues.rb b/features/steps/project/issues.rb
index a92fd50584d..a92fd50584d 100644
--- a/features/steps/project/project_issues.rb
+++ b/features/steps/project/issues.rb
diff --git a/features/steps/project/project_labels.rb b/features/steps/project/labels.rb
index 0907cdb526f..0907cdb526f 100644
--- a/features/steps/project/project_labels.rb
+++ b/features/steps/project/labels.rb
diff --git a/features/steps/project/project_markdown_render.rb b/features/steps/project/markdown_render.rb
index 89fbb7408c2..c94de283069 100644
--- a/features/steps/project/project_markdown_render.rb
+++ b/features/steps/project/markdown_render.rb
@@ -1,3 +1,6 @@
+# If you need to modify the existing seed repository for your tests,
+# it is recommended that you make the changes on the `markdown` branch of the seed project repository,
+# which should only be used by tests in this file. See `/spec/factories.rb#project` for more info.
class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
@@ -50,7 +53,7 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
end
Then 'I should see correct doc/api directory rendered' do
- current_path.should == project_tree_path(@project, "master/doc/api/")
+ current_path.should == project_tree_path(@project, "master/doc/api")
page.should have_content "README.md"
page.should have_content "users.md"
end
@@ -64,6 +67,18 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
page.should have_content "bundle exec rake gitlab:env:info RAILS_ENV=production"
end
+ And 'I click on link "empty" in the README' do
+ within('.readme-holder') do
+ click_link "empty"
+ end
+ end
+
+ And 'I click on link "id" in the README' do
+ within('.readme-holder') do
+ click_link "#id"
+ end
+ end
+
And 'I navigate to the doc/api/README' do
click_link "doc"
click_link "api"
@@ -90,10 +105,24 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
click_link "Rake tasks"
end
+ # Markdown branch
+
When 'I visit markdown branch' do
visit project_tree_path(@project, "markdown")
end
+ When 'I visit markdown branch "README.md" blob' do
+ visit project_blob_path(@project, "markdown/README.md")
+ end
+
+ When 'I visit markdown branch "d" tree' do
+ visit project_tree_path(@project, "markdown/d")
+ end
+
+ When 'I visit markdown branch "d/README.md" blob' do
+ visit project_blob_path(@project, "markdown/d/README.md")
+ end
+
Then 'I should see files from repository in markdown branch' do
current_path.should == project_tree_path(@project, "markdown")
page.should have_content "Gemfile"
@@ -124,13 +153,57 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
page.should have_content "Get a list of users."
end
+ # Expected link contents
+
+ Then 'The link with text "empty" should have url "tree/markdown"' do
+ find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown")
+ end
+
+ Then 'The link with text "empty" should have url "blob/markdown/README.md"' do
+ find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md")
+ end
+
+ Then 'The link with text "empty" should have url "tree/markdown/d"' do
+ find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown/d")
+ end
+
+ Then 'The link with text "empty" should have url "blob/markdown/d/README.md"' do
+ find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/d/README.md")
+ end
+
+ Then 'The link with text "ID" should have url "tree/markdownID"' do
+ find('a', text: /^#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
+ end
+
+ Then 'The link with text "/ID" should have url "tree/markdownID"' do
+ find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
+ end
+
+ Then 'The link with text "README.mdID" should have url "blob/markdown/README.mdID"' do
+ find('a', text: /^README.md#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
+ end
+
+ Then 'The link with text "d/README.mdID" should have url "blob/markdown/d/README.mdID"' do
+ find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id'
+ end
+
+ Then 'The link with text "ID" should have url "blob/markdown/README.mdID"' do
+ find('a', text: /^#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
+ end
+
+ Then 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do
+ find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
+ end
+
+ # Wiki
+
Given 'I go to wiki page' do
click_link "Wiki"
current_path.should == project_wiki_path(@project, "home")
end
And 'I add various links to the wiki page' do
- fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](doc/api/README.md)\n[Rake tasks](doc/raketasks)\n"
+ fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n"
fill_in "wiki[message]", with: "Adding links to wiki"
click_button "Create page"
end
@@ -169,8 +242,8 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
end
Then 'I see Gitlab API document' do
- current_path.should == project_blob_path(@project, "master/doc/api/README.md")
- page.should have_content "Status codes"
+ current_path.should == project_wiki_path(@project, "api")
+ page.should have_content "Editing"
end
And 'I click on Rake tasks link' do
@@ -178,9 +251,8 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
end
Then 'I see Rake tasks directory' do
- current_path.should == project_tree_path(@project, "master/doc/raketasks")
- page.should have_content "backup_restore.md"
- page.should have_content "maintenance.md"
+ current_path.should == project_wiki_path(@project, "raketasks")
+ page.should have_content "Editing"
end
Given 'I go directory which contains README file' do
diff --git a/features/steps/project/project_merge_requests.rb b/features/steps/project/merge_requests.rb
index fcbae8b8fb0..f42eb6377ce 100644
--- a/features/steps/project/project_merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -55,25 +55,15 @@ class ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I click link "Close"' do
- click_link "Close"
+ within '.page-title' do
+ click_link "Close"
+ end
end
step 'I submit new merge request "Wiki Feature"' do
fill_in "merge_request_title", with: "Wiki Feature"
-
- # this must come first, so that the target branch is set
- # by the time the "select" for "notes_refactoring" is executed
- select project.path_with_namespace, from: "merge_request_target_project_id"
select "master", from: "merge_request_source_branch"
-
- find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s
- find(:select, "merge_request_source_project_id", {}).value.should == project.id.to_s
-
- # using "notes_refactoring" because "Bug NS-04" uses master/stable,
- # this will fail merge_request validation if the branches are the same
- find(:select, "merge_request_target_branch", {}).find(:option, "notes_refactoring", {}).value.should == "notes_refactoring"
select "notes_refactoring", from: "merge_request_target_branch"
-
click_button "Submit merge request"
end
@@ -175,11 +165,79 @@ class ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see merged request' do
- within '.page-title' do
+ within '.issue-box' do
page.should have_content "Merged"
end
end
+ step 'I click link "Reopen"' do
+ within '.page-title' do
+ click_link "Reopen"
+ end
+ end
+
+ step 'I should see reopened merge request "Bug NS-04"' do
+ within '.state-label' do
+ page.should have_content "Open"
+ end
+ end
+
+ step 'I click link "Hide inline discussion" of the second file' do
+ within '.files [id^=diff]:nth-child(2)' do
+ click_link "Diff comments"
+ end
+ end
+
+ step 'I click link "Show inline discussion" of the second file' do
+ within '.files [id^=diff]:nth-child(2)' do
+ click_link "Diff comments"
+ end
+ end
+
+ step 'I should not see a comment like "Line is wrong" in the second file' do
+ within '.files [id^=diff]:nth-child(2)' do
+ page.should_not have_visible_content "Line is wrong"
+ end
+ end
+
+ step 'I should see a comment like "Line is wrong" in the second file' do
+ within '.files [id^=diff]:nth-child(2) .note-text' do
+ page.should have_visible_content "Line is wrong"
+ end
+ end
+
+ step 'I leave a comment like "Line is correct" on line 12 of the first file' do
+ init_diff_note_first_file
+
+ within(".js-discussion-note-form") do
+ fill_in "note_note", with: "Line is correct"
+ click_button "Add Comment"
+ end
+
+ within ".files [id^=diff]:nth-child(1) .note-text" do
+ page.should have_content "Line is correct"
+ end
+ end
+
+ step 'I leave a comment like "Line is wrong" on line 39 of the second file' do
+ init_diff_note_second_file
+
+ within(".js-discussion-note-form") do
+ fill_in "note_note", with: "Line is wrong"
+ click_button "Add Comment"
+ end
+
+ within ".files [id^=diff]:nth-child(2) .note-text" do
+ page.should have_content "Line is wrong"
+ end
+ end
+
+ step 'I should still see a comment like "Line is correct" in the first file' do
+ within '.files [id^=diff]:nth-child(1) .note-text' do
+ page.should have_visible_content "Line is correct"
+ end
+ end
+
def project
@project ||= Project.find_by!(name: "Shop")
end
@@ -202,4 +260,16 @@ class ProjectMergeRequests < Spinach::FeatureSteps
page.should have_content message
end
end
+
+ def init_diff_note_first_file
+ find('a[data-line-code="a5cc2925ca8258af241be7e5b0381edf30266302_12_12"]').click
+ end
+
+ def init_diff_note_second_file
+ find('a[data-line-code="8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_28_39"]').click
+ end
+
+ def have_visible_content (text)
+ have_css("*", text: text, visible: true)
+ end
end
diff --git a/features/steps/project/project_milestones.rb b/features/steps/project/milestones.rb
index 9ce18fbaabd..9ce18fbaabd 100644
--- a/features/steps/project/project_milestones.rb
+++ b/features/steps/project/milestones.rb
diff --git a/features/steps/project/project_multiselect_blob.rb b/features/steps/project/multiselect_blob.rb
index 3d330e837c1..3d330e837c1 100644
--- a/features/steps/project/project_multiselect_blob.rb
+++ b/features/steps/project/multiselect_blob.rb
diff --git a/features/steps/project/project_network_graph.rb b/features/steps/project/network_graph.rb
index 1c5cfcc6c68..1c5cfcc6c68 100644
--- a/features/steps/project/project_network_graph.rb
+++ b/features/steps/project/network_graph.rb
diff --git a/features/steps/project/project_browse_branches.rb b/features/steps/project/project_browse_branches.rb
deleted file mode 100644
index ef29cc67a4e..00000000000
--- a/features/steps/project/project_browse_branches.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-class ProjectBrowseBranches < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- Then 'I should see "Shop" recent branches list' do
- page.should have_content "Branches"
- page.should have_content "master"
- end
-
- Given 'I click link "All"' do
- click_link "All"
- end
-
- Then 'I should see "Shop" all branches list' do
- page.should have_content "Branches"
- page.should have_content "master"
- end
-
- Given 'I click link "Protected"' do
- click_link "Protected"
- end
-
- Then 'I should see "Shop" protected branches list' do
- within ".protected-branches-list" do
- page.should have_content "stable"
- page.should_not have_content "master"
- end
- end
-
- And 'project "Shop" has protected branches' do
- project = Project.find_by(name: "Shop")
- project.protected_branches.create(name: "stable")
- end
-end
diff --git a/features/steps/project/public_projects.rb b/features/steps/project/public.rb
index 7063e7d56ae..7063e7d56ae 100644
--- a/features/steps/project/public_projects.rb
+++ b/features/steps/project/public.rb
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
index 76ffea1bb6f..cfa4ce82be3 100644
--- a/features/steps/project/redirects.rb
+++ b/features/steps/project/redirects.rb
@@ -4,7 +4,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
include SharedProject
step 'public project "Community"' do
- create :project, name: 'Community', visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ create :project, :public, name: 'Community'
end
step 'private project "Enterprise"' do
diff --git a/features/steps/project/project_search_code.rb b/features/steps/project/search_code.rb
index d117b019a15..d117b019a15 100644
--- a/features/steps/project/project_search_code.rb
+++ b/features/steps/project/search_code.rb
diff --git a/features/steps/project/project_services.rb b/features/steps/project/services.rb
index 54b3f18e084..0594a08a5e7 100644
--- a/features/steps/project/project_services.rb
+++ b/features/steps/project/services.rb
@@ -100,4 +100,22 @@ class ProjectServices < Spinach::FeatureSteps
step 'I should see email on push service settings saved' do
find_field('Recipients').value.should == 'qa@company.name'
end
+
+ step 'I click Slack service link' do
+ click_link 'Slack'
+ end
+
+ step 'I fill Slack settings' do
+ check 'Active'
+ fill_in 'Subdomain', with: 'gitlab'
+ fill_in 'Room', with: '#gitlab'
+ fill_in 'Token', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see Slack service settings saved' do
+ find_field('Subdomain').value.should == 'gitlab'
+ find_field('Room').value.should == '#gitlab'
+ find_field('Token').value.should == 'verySecret'
+ end
end
diff --git a/features/steps/project/project_snippets.rb b/features/steps/project/snippets.rb
index c3a76bea269..c3a76bea269 100644
--- a/features/steps/project/project_snippets.rb
+++ b/features/steps/project/snippets.rb
diff --git a/features/steps/project/project_team_management.rb b/features/steps/project/team_management.rb
index ffc5016529f..ffc5016529f 100644
--- a/features/steps/project/project_team_management.rb
+++ b/features/steps/project/team_management.rb
diff --git a/features/steps/project/project_wall.rb b/features/steps/project/wall.rb
index 7c61580eb2c..7c61580eb2c 100644
--- a/features/steps/project/project_wall.rb
+++ b/features/steps/project/wall.rb
diff --git a/features/steps/project/project_wiki.rb b/features/steps/project/wiki.rb
index 6146599cc4a..6146599cc4a 100644
--- a/features/steps/project/project_wiki.rb
+++ b/features/steps/project/wiki.rb
diff --git a/features/steps/public/groups_feature.rb b/features/steps/public/groups_feature.rb
new file mode 100644
index 00000000000..015deca5427
--- /dev/null
+++ b/features/steps/public/groups_feature.rb
@@ -0,0 +1,93 @@
+class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedGroup
+ include SharedProject
+
+ step 'group "TestGroup" has private project "Enterprise"' do
+ group_has_project("TestGroup", "Enterprise", Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ step 'group "TestGroup" has internal project "Internal"' do
+ group_has_project("TestGroup", "Internal", Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ step 'group "TestGroup" has public project "Community"' do
+ group_has_project("TestGroup", "Community", Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ step '"John Doe" is owner of group "TestGroup"' do
+ group = Group.find_by(name: "TestGroup") || create(:group, name: "TestGroup")
+ user = create(:user, name: "John Doe")
+ group.add_user(user, Gitlab::Access::OWNER)
+ end
+
+ step 'I visit group "TestGroup" page' do
+ visit group_path(Group.find_by(name: "TestGroup"))
+ end
+
+ step 'I visit group "TestGroup" issues page' do
+ visit issues_group_path(Group.find_by(name: "TestGroup"))
+ end
+
+ step 'I visit group "TestGroup" merge requests page' do
+ visit merge_requests_group_path(Group.find_by(name: "TestGroup"))
+ end
+
+ step 'I visit group "TestGroup" members page' do
+ visit members_group_path(Group.find_by(name: "TestGroup"))
+ end
+
+ step 'I should not see project "Enterprise" items' do
+ page.should_not have_content "Enterprise"
+ end
+
+ step 'I should see project "Internal" items' do
+ page.should have_content "Internal"
+ end
+
+ step 'I should not see project "Internal" items' do
+ page.should_not have_content "Internal"
+ end
+
+ step 'I should see project "Community" items' do
+ page.should have_content "Community"
+ end
+
+ step 'I change filter to Everyone\'s' do
+ click_link "Everyone's"
+ end
+
+ step 'I should see group member "John Doe"' do
+ page.should have_content "John Doe"
+ end
+
+ step 'I should not see member roles' do
+ page.body.should_not match(%r{owner|developer|reporter|guest}i)
+ end
+
+ protected
+
+ def group_has_project(groupname, projectname, visibility_level)
+ group = Group.find_by(name: groupname) || create(:group, name: groupname)
+ project = create(:project,
+ namespace: group,
+ name: projectname,
+ path: "#{groupname}-#{projectname}",
+ visibility_level: visibility_level
+ )
+ create(:issue,
+ title: "#{projectname} feature",
+ project: project
+ )
+ create(:merge_request,
+ title: "#{projectname} feature implemented",
+ source_project: project,
+ target_project: project
+ )
+ create(:closed_issue_event,
+ project: project
+ )
+ end
+end
+
diff --git a/features/steps/public/projects_feature.rb b/features/steps/public/projects.rb
index eb1d235f435..7c7311bb91c 100644
--- a/features/steps/public/projects_feature.rb
+++ b/features/steps/public/projects.rb
@@ -4,7 +4,7 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps
include SharedProject
step 'public empty project "Empty Public Project"' do
- create :empty_project, name: 'Empty Public Project', visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ create :empty_project, :public, name: 'Empty Public Project'
end
step 'I should see project "Empty Public Project"' do
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 9c39a226e1b..f80d8d06475 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -2,7 +2,7 @@ module SharedDiffNote
include Spinach::DSL
Given 'I cancel the diff comment' do
- within(".file") do
+ within(".diff-file") do
find(".js-close-discussion-note-form").click
end
end
@@ -13,14 +13,14 @@ module SharedDiffNote
end
Given 'I haven\'t written any diff comment text' do
- within(".file") do
+ within(".diff-file") do
fill_in "note[note]", with: ""
end
end
Given 'I leave a diff comment like "Typo, please fix"' do
find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click
- within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do
+ within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do
fill_in "note[note]", with: "Typo, please fix"
find(".js-comment-button").trigger("click")
sleep 0.05
@@ -29,7 +29,7 @@ module SharedDiffNote
Given 'I preview a diff comment text like "Should fix it :smile:"' do
find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click
- within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do
+ within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do
fill_in "note[note]", with: "Should fix it :smile:"
find(".js-note-preview-button").trigger("click")
end
@@ -38,7 +38,7 @@ module SharedDiffNote
Given 'I preview another diff comment text like "DRY this up"' do
find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_57_41"]').click
- within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_57_41']") do
+ within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_57_41']") do
fill_in "note[note]", with: "DRY this up"
find(".js-note-preview-button").trigger("click")
end
@@ -53,13 +53,13 @@ module SharedDiffNote
end
Given 'I write a diff comment like ":-1: I don\'t like this"' do
- within(".file") do
+ within(".diff-file") do
fill_in "note[note]", with: ":-1: I don\'t like this"
end
end
Given 'I submit the diff comment' do
- within(".file") do
+ within(".diff-file") do
click_button("Add Comment")
end
end
@@ -67,49 +67,49 @@ module SharedDiffNote
Then 'I should not see the diff comment form' do
- within(".file") do
+ within(".diff-file") do
page.should_not have_css("form.new_note")
end
end
Then 'I should not see the diff comment preview button' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-note-preview-button", visible: false)
end
end
Then 'I should not see the diff comment text field' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-note-text", visible: false)
end
end
Then 'I should only see one diff form' do
- within(".file") do
+ within(".diff-file") do
page.should have_css("form.new_note", count: 1)
end
end
Then 'I should see a diff comment form with ":-1: I don\'t like this"' do
- within(".file") do
+ within(".diff-file") do
page.should have_field("note[note]", with: ":-1: I don\'t like this")
end
end
Then 'I should see a diff comment saying "Typo, please fix"' do
- within(".file .note") do
+ within(".diff-file .note") do
page.should have_content("Typo, please fix")
end
end
Then 'I should see a discussion reply button' do
- within(".file") do
+ within(".diff-file") do
page.should have_link("Reply")
end
end
Then 'I should see a temporary diff comment form' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-temp-notes-holder form.new_note")
end
end
@@ -119,37 +119,37 @@ module SharedDiffNote
end
Then 'I should see an empty diff comment form' do
- within(".file") do
+ within(".diff-file") do
page.should have_field("note[note]", with: "")
end
end
Then 'I should see the cancel comment button' do
- within(".file form") do
+ within(".diff-file form") do
page.should have_css(".js-close-discussion-note-form", text: "Cancel")
end
end
Then 'I should see the diff comment preview' do
- within(".file form") do
+ within(".diff-file form") do
page.should have_css(".js-note-preview", visible: false)
end
end
Then 'I should see the diff comment edit button' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-note-edit-button", visible: true)
end
end
Then 'I should see the diff comment preview button' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-note-preview-button", visible: true)
end
end
Then 'I should see two separate previews' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-note-preview", visible: true, count: 2)
page.should have_content("Should fix it")
page.should have_content("DRY this up")
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index f35beab8af2..f8cb753b78f 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -79,7 +79,7 @@ module SharedProject
end
step 'internal project "Internal"' do
- create :project, name: 'Internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL
+ create :project, :internal, name: 'Internal'
end
step 'I should see project "Internal"' do
@@ -91,7 +91,7 @@ module SharedProject
end
step 'public project "Community"' do
- create :project, name: 'Community', visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ create :project, :public, name: 'Community'
end
step 'I should see project "Community"' do
@@ -112,14 +112,14 @@ module SharedProject
step '"John Doe" is authorized to internal project "Internal"' do
user = user_exists("John Doe", username: "john_doe")
project = Project.find_by(name: "Internal")
- project ||= create :project, name: 'Internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL
+ project ||= create :project, :internal, name: 'Internal'
project.team << [user, :master]
end
step '"John Doe" is authorized to public project "Community"' do
user = user_exists("John Doe", username: "john_doe")
project = Project.find_by(name: "Community")
- project ||= create :project, name: 'Community', visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ project ||= create :project, :public, name: 'Community'
project.team << [user, :master]
end
end
diff --git a/features/steps/snippets/discover_snippets.rb b/features/steps/snippets/discover.rb
index 09337937002..09337937002 100644
--- a/features/steps/snippets/discover_snippets.rb
+++ b/features/steps/snippets/discover.rb
diff --git a/features/steps/snippets/user_snippets.rb b/features/steps/snippets/user.rb
index 2d7ffc866e7..2d7ffc866e7 100644
--- a/features/steps/snippets/user_snippets.rb
+++ b/features/steps/snippets/user.rb
diff --git a/features/support/env.rb b/features/support/env.rb
index 0186002c559..7b11f5a7c6f 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -15,7 +15,7 @@ require 'spinach/capybara'
require 'sidekiq/testing/inline'
-%w(valid_commit big_commits select2_helper test_env).each do |f|
+%w(valid_commit valid_commit_with_alt_email big_commits select2_helper test_env).each do |f|
require Rails.root.join('spec', 'support', f)
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 283f7642f67..7c4cdad7f0d 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -22,6 +22,8 @@ module API
end
format :json
+ content_type :txt, "text/plain"
+
helpers APIHelpers
mount Groups
@@ -36,10 +38,12 @@ module API
mount Internal
mount SystemHooks
mount ProjectSnippets
+ mount ProjectMembers
mount DeployKeys
mount ProjectHooks
mount Services
mount Files
+ mount Commits
mount Namespaces
end
end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
new file mode 100644
index 00000000000..33b8b3d2244
--- /dev/null
+++ b/lib/api/commits.rb
@@ -0,0 +1,64 @@
+require 'mime/types'
+
+module API
+ # Projects API
+ class Commits < Grape::API
+ before { authenticate! }
+ before { authorize! :download_code, user_project }
+
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+ # Get a project repository commits
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
+ # Example Request:
+ # GET /projects/:id/repository/commits
+ get ":id/repository/commits" do
+ page = (params[:page] || 0).to_i
+ per_page = (params[:per_page] || 20).to_i
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+
+ commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
+ present commits, with: Entities::RepoCommit
+ end
+
+ # Get a specific commit of a project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit hash or name of a repository branch or tag
+ # Example Request:
+ # GET /projects/:id/repository/commits/:sha
+ get ":id/repository/commits/:sha" do
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! "Commit" unless commit
+ present commit, with: Entities::RepoCommitDetail
+ end
+
+ # Get the diff for a specific commit of a project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit or branch name
+ # Example Request:
+ # GET /projects/:id/repository/commits/:sha/diff
+ get ":id/repository/commits/:sha/diff" do
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! "Commit" unless commit
+ commit.diffs
+ end
+ end
+ end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 8f54d0d4d84..9fa8506926c 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -6,10 +6,16 @@ module API
expose :is_admin?, as: :is_admin
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
+
+ expose :avatar_url do |user, options|
+ if user.avatar.present?
+ user.avatar.url
+ end
+ end
end
class UserSafe < Grape::Entity
- expose :name
+ expose :name, :username
end
class UserBasic < Grape::Entity
@@ -38,7 +44,7 @@ module API
expose :id, :description, :default_branch
expose :public?, as: :public
expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url
- expose :owner, using: Entities::UserBasic
+ expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
expose :name, :name_with_namespace
expose :path, :path_with_namespace
expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at
@@ -52,18 +58,6 @@ module API
end
end
- class TeamMember < UserBasic
- expose :permission, as: :access_level do |user, options|
- options[:user_team].user_team_user_relationships.find_by(user_id: user.id).permission
- end
- end
-
- class TeamProject < Project
- expose :greatest_access, as: :greatest_access_level do |project, options|
- options[:user_team].user_team_project_relationships.find_by(project_id: project.id).greatest_access
- end
- end
-
class Group < Grape::Entity
expose :id, :name, :path, :owner_id
end
@@ -138,7 +132,7 @@ module API
end
class MergeRequest < ProjectEntity
- expose :target_branch, :source_branch, :title, :state, :upvotes, :downvotes
+ expose :target_branch, :source_branch, :title, :state, :upvotes, :downvotes, :description
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
end
@@ -169,5 +163,33 @@ module API
class Namespace < Grape::Entity
expose :id, :path, :kind
end
+
+ class ProjectAccess < Grape::Entity
+ expose :project_access, as: :access_level
+ expose :notification_level
+ end
+
+ class GroupAccess < Grape::Entity
+ expose :group_access, as: :access_level
+ expose :notification_level
+ end
+
+ class ProjectWithAccess < Project
+ expose :permissions do
+ expose :project_access, using: Entities::ProjectAccess do |project, options|
+ project.users_projects.find_by(user_id: options[:user].id)
+ end
+
+ expose :group_access, using: Entities::GroupAccess do |project, options|
+ if project.group
+ project.group.users_groups.find_by(user_id: options[:user].id)
+ end
+ end
+ end
+ end
+
+ class Label < Grape::Entity
+ expose :name
+ end
end
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 213604915a6..e0c46f92b84 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -5,10 +5,61 @@ module API
before { authorize! :push_code, user_project }
resource :projects do
+ # Get file from repository
+ # File content is Base64 encoded
+ #
+ # Parameters:
+ # file_path (required) - The path to the file. Ex. lib/class.rb
+ # ref (required) - The name of branch, tag or commit
+ #
+ # Example Request:
+ # GET /projects/:id/repository/files
+ #
+ # Example response:
+ # {
+ # "file_name": "key.rb",
+ # "file_path": "app/models/key.rb",
+ # "size": 1476,
+ # "encoding": "base64",
+ # "content": "IyA9PSBTY2hlbWEgSW5mb3...",
+ # "ref": "master",
+ # "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83",
+ # "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50"
+ # }
+ #
+ get ":id/repository/files" do
+ required_attributes! [:file_path, :ref]
+ attrs = attributes_for_keys [:file_path, :ref]
+ ref = attrs.delete(:ref)
+ file_path = attrs.delete(:file_path)
+
+ commit = user_project.repository.commit(ref)
+ not_found! "Commit" unless commit
+
+ blob = user_project.repository.blob_at(commit.sha, file_path)
+
+ if blob
+ status(200)
+
+ {
+ file_name: blob.name,
+ file_path: blob.path,
+ size: blob.size,
+ encoding: "base64",
+ content: Base64.encode64(blob.data),
+ ref: ref,
+ blob_id: blob.id,
+ commit_id: commit.id,
+ }
+ else
+ render_api_error!('File not found', 404)
+ end
+ end
+
# Create new file in repository
#
# Parameters:
- # file_path (optional) - The path to new file. Ex. lib/class.rb
+ # file_path (required) - The path to new file. Ex. lib/class.rb
# branch_name (required) - The name of branch
# content (required) - File content
# commit_message (required) - Commit message
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index f8c48e2f3b2..fc309f65a56 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -47,7 +47,7 @@ module API
end
def find_project(id)
- project = Project.find_by(id: id) || Project.find_with_namespace(id)
+ project = Project.find_with_namespace(id) || Project.find_by(id: id)
if project && can?(current_user, :read_project, project)
project
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index ed6b50c3a6a..bcf97574673 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -1,16 +1,12 @@
module API
# Internal access API
class Internal < Grape::API
-
- DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
- PUSH_COMMANDS = %w{ git-receive-pack }
-
namespace 'internal' do
- #
- # Check if ssh key has access to project code
+ # Check if git command is allowed to project
#
# Params:
- # key_id - SSH Key id
+ # key_id - ssh key id for Git over SSH
+ # user_id - user id for Git over HTTP
# project - project path with namespace
# action - git action (git-upload-pack or git-receive-pack)
# ref - branch name
@@ -22,35 +18,25 @@ module API
# the wiki repository as well.
project_path = params[:project]
project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/
-
- key = Key.find(params[:key_id])
project = Project.find_with_namespace(project_path)
- git_cmd = params[:action]
return false unless project
-
- if key.is_a? DeployKey
- key.projects.include?(project) && DOWNLOAD_COMMANDS.include?(git_cmd)
- else
- user = key.user
-
- return false if user.blocked?
- return false if user.ldap_user? && Gitlab::LDAP::User.blocked?(user.extern_uid)
-
- action = case git_cmd
- when *DOWNLOAD_COMMANDS
- then :download_code
- when *PUSH_COMMANDS
- then
- if project.protected_branch?(params[:ref])
- :push_code_to_protected_branches
- else
- :push_code
- end
- end
-
- user.can?(action, project)
- end
+ actor = if params[:key_id]
+ Key.find(params[:key_id])
+ elsif params[:user_id]
+ User.find(params[:user_id])
+ end
+
+ return false unless actor
+
+ Gitlab::GitAccess.new.allowed?(
+ actor,
+ params[:action],
+ project,
+ params[:ref],
+ params[:oldrev],
+ params[:newrev]
+ )
end
#
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 0f62cac9a0c..3a1a00d0719 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -64,6 +64,7 @@ module API
# target_project - The target project of the merge request defaults to the :id of the project
# assignee_id - Assignee user ID
# title (required) - Title of MR
+ # description - Description of MR
#
# Example:
# POST /projects/:id/merge_requests
@@ -72,7 +73,7 @@ module API
set_current_user_for_thread do
authorize! :write_merge_request, user_project
required_attributes! [:source_branch, :target_branch, :title]
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id]
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
merge_request = user_project.merge_requests.new(attrs)
merge_request.author = current_user
merge_request.source_project = user_project
@@ -105,19 +106,18 @@ module API
# assignee_id - Assignee user ID
# title - Title of MR
# state_event - Status of MR. (close|reopen|merge)
+ # description - Description of MR
# Example:
# PUT /projects/:id/merge_request/:merge_request_id
#
put ":id/merge_request/:merge_request_id" do
set_current_user_for_thread do
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event]
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :modify_merge_request, merge_request
if merge_request.update_attributes attrs
- merge_request.reload_code
- merge_request.mark_as_unchecked
present merge_request, with: Entities::MergeRequest
else
handle_merge_request_errors! merge_request.errors
@@ -125,6 +125,22 @@ module API
end
end
+ # Get a merge request's comments
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # Examples:
+ # GET /projects/:id/merge_request/:merge_request_id/comments
+ #
+ get ":id/merge_request/:merge_request_id/comments" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ authorize! :read_merge_request, merge_request
+
+ present paginate(merge_request.notes), with: Entities::MRNote
+ end
+
# Post comment to merge request
#
# Parameters:
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index c271dd8b61b..79c3d122d32 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -5,15 +5,6 @@ module API
before { authorize_admin_project }
resource :projects do
- helpers do
- def handle_project_member_errors(errors)
- if errors[:project_access].any?
- error!(errors[:project_access], 422)
- end
- not_found!
- end
- end
-
# Get project hooks
#
# Parameters:
diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb
new file mode 100644
index 00000000000..47c4ddce163
--- /dev/null
+++ b/lib/api/project_members.rb
@@ -0,0 +1,114 @@
+module API
+ # Projects members API
+ class ProjectMembers < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+ # Get a project team members
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # query - Query string
+ # Example Request:
+ # GET /projects/:id/members
+ get ":id/members" do
+ if params[:query].present?
+ @members = paginate user_project.users.where("username LIKE ?", "%#{params[:query]}%")
+ else
+ @members = paginate user_project.users
+ end
+ present @members, with: Entities::ProjectMember, project: user_project
+ end
+
+ # Get a project team members
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # user_id (required) - The ID of a user
+ # Example Request:
+ # GET /projects/:id/members/:user_id
+ get ":id/members/:user_id" do
+ @member = user_project.users.find params[:user_id]
+ present @member, with: Entities::ProjectMember, project: user_project
+ end
+
+ # Add a new project team member
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # user_id (required) - The ID of a user
+ # access_level (required) - Project access level
+ # Example Request:
+ # POST /projects/:id/members
+ post ":id/members" do
+ authorize! :admin_project, user_project
+ required_attributes! [:user_id, :access_level]
+
+ # either the user is already a team member or a new one
+ team_member = user_project.team_member_by_id(params[:user_id])
+ if team_member.nil?
+ team_member = user_project.users_projects.new(
+ user_id: params[:user_id],
+ project_access: params[:access_level]
+ )
+ end
+
+ if team_member.save
+ @member = team_member.user
+ present @member, with: Entities::ProjectMember, project: user_project
+ else
+ handle_project_member_errors team_member.errors
+ end
+ end
+
+ # Update project team member
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # user_id (required) - The ID of a team member
+ # access_level (required) - Project access level
+ # Example Request:
+ # PUT /projects/:id/members/:user_id
+ put ":id/members/:user_id" do
+ authorize! :admin_project, user_project
+ required_attributes! [:access_level]
+
+ team_member = user_project.users_projects.find_by(user_id: params[:user_id])
+ not_found!("User can not be found") if team_member.nil?
+
+ if team_member.update_attributes(project_access: params[:access_level])
+ @member = team_member.user
+ present @member, with: Entities::ProjectMember, project: user_project
+ else
+ handle_project_member_errors team_member.errors
+ end
+ end
+
+ # Remove a team member from project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # user_id (required) - The ID of a team member
+ # Example Request:
+ # DELETE /projects/:id/members/:user_id
+ delete ":id/members/:user_id" do
+ authorize! :admin_project, user_project
+ team_member = user_project.users_projects.find_by(user_id: params[:user_id])
+ unless team_member.nil?
+ team_member.destroy
+ else
+ {message: "Access revoked", id: params[:user_id].to_i}
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 888aa7e77d2..9d290c75ba9 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -5,13 +5,6 @@ module API
resource :projects do
helpers do
- def handle_project_member_errors(errors)
- if errors[:project_access].any?
- error!(errors[:project_access], 422)
- end
- not_found!
- end
-
def map_public_to_visibility_level(attrs)
publik = attrs.delete(:public)
publik = [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(publik)
@@ -55,7 +48,7 @@ module API
# Example Request:
# GET /projects/:id
get ":id" do
- present user_project, with: Entities::Project
+ present user_project, with: Entities::ProjectWithAccess, user: current_user
end
# Get a single project events
@@ -196,117 +189,42 @@ module API
user_project.forked_project_link.destroy
end
end
-
- # Get a project team members
- #
- # Parameters:
- # id (required) - The ID of a project
- # query - Query string
- # Example Request:
- # GET /projects/:id/members
- get ":id/members" do
- if params[:query].present?
- @members = paginate user_project.users.where("username LIKE ?", "%#{params[:query]}%")
- else
- @members = paginate user_project.users
- end
- present @members, with: Entities::ProjectMember, project: user_project
- end
-
- # Get a project team members
+ # search for projects current_user has access to
#
# Parameters:
- # id (required) - The ID of a project
- # user_id (required) - The ID of a user
+ # 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
# Example Request:
- # GET /projects/:id/members/:user_id
- get ":id/members/:user_id" do
- @member = user_project.users.find params[:user_id]
- present @member, with: Entities::ProjectMember, project: user_project
+ # GET /projects/search/:query
+ get "/search/:query" do
+ 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]}%")
+ present paginate(projects), with: Entities::Project
end
- # Add a new project team member
- #
- # Parameters:
- # id (required) - The ID of a project
- # user_id (required) - The ID of a user
- # access_level (required) - Project access level
- # Example Request:
- # POST /projects/:id/members
- post ":id/members" do
- authorize! :admin_project, user_project
- required_attributes! [:user_id, :access_level]
-
- # either the user is already a team member or a new one
- team_member = user_project.team_member_by_id(params[:user_id])
- if team_member.nil?
- team_member = user_project.users_projects.new(
- user_id: params[:user_id],
- project_access: params[:access_level]
- )
- end
-
- if team_member.save
- @member = team_member.user
- present @member, with: Entities::ProjectMember, project: user_project
- else
- handle_project_member_errors team_member.errors
- end
- end
- # Update project team member
+ # Get a users list
#
- # Parameters:
- # id (required) - The ID of a project
- # user_id (required) - The ID of a team member
- # access_level (required) - Project access level
# Example Request:
- # PUT /projects/:id/members/:user_id
- put ":id/members/:user_id" do
- authorize! :admin_project, user_project
- required_attributes! [:access_level]
-
- team_member = user_project.users_projects.find_by(user_id: params[:user_id])
- not_found!("User can not be found") if team_member.nil?
-
- if team_member.update_attributes(project_access: params[:access_level])
- @member = team_member.user
- present @member, with: Entities::ProjectMember, project: user_project
- else
- handle_project_member_errors team_member.errors
- end
+ # GET /users
+ get ':id/users' do
+ @users = User.where(id: user_project.team.users.map(&:id))
+ @users = @users.search(params[:search]) if params[:search].present?
+ @users = paginate @users
+ present @users, with: Entities::User
end
- # Remove a team member from project
+ # Get a project labels
#
# Parameters:
# id (required) - The ID of a project
- # user_id (required) - The ID of a team member
# Example Request:
- # DELETE /projects/:id/members/:user_id
- delete ":id/members/:user_id" do
- authorize! :admin_project, user_project
- team_member = user_project.users_projects.find_by(user_id: params[:user_id])
- unless team_member.nil?
- team_member.destroy
- else
- {message: "Access revoked", id: params[:user_id].to_i}
- end
- end
-
- # search for projects current_user has access to
- #
- # 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
- # Example Request:
- # GET /projects/search/:query
- get "/search/:query" do
- 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]}%")
- present paginate(projects), with: Entities::Project
+ # GET /projects/:id/labels
+ get ':id/labels' do
+ @labels = user_project.issues_labels
+ present @labels, with: Entities::Label
end
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index cad64760abb..ba53bf9baa4 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -85,50 +85,6 @@ module API
present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project
end
- # Get a project repository commits
- #
- # Parameters:
- # id (required) - The ID of a project
- # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
- # Example Request:
- # GET /projects/:id/repository/commits
- get ":id/repository/commits" do
- page = (params[:page] || 0).to_i
- per_page = (params[:per_page] || 20).to_i
- ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
-
- commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
- present commits, with: Entities::RepoCommit
- end
-
- # Get a specific commit of a project
- #
- # Parameters:
- # id (required) - The ID of a project
- # sha (required) - The commit hash or name of a repository branch or tag
- # Example Request:
- # GET /projects/:id/repository/commits/:sha
- get ":id/repository/commits/:sha" do
- sha = params[:sha]
- commit = user_project.repository.commit(sha)
- not_found! "Commit" unless commit
- present commit, with: Entities::RepoCommitDetail
- end
-
- # Get the diff for a specific commit of a project
- #
- # Parameters:
- # id (required) - The ID of a project
- # sha (required) - The commit or branch name
- # Example Request:
- # GET /projects/:id/repository/commits/:sha/diff
- get ":id/repository/commits/:sha/diff" do
- sha = params[:sha]
- commit = user_project.repository.commit(sha)
- not_found! "Commit" unless commit
- commit.diffs
- end
-
# Get a project repository tree
#
# Parameters:
@@ -167,9 +123,7 @@ module API
blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath])
not_found! "File" unless blob
- env['api.format'] = :txt
-
- content_type blob.mime_type
+ content_type 'text/plain'
present blob.data
end
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index ebb4f289c52..7b6908ccad8 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -30,6 +30,9 @@ module Backup
system('mysql', *mysql_args, config['database'], in: db_file_name)
when "postgresql" then
print "Restoring PostgreSQL database #{config['database']} ... "
+ # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
+ # statements like MySQL.
+ Rake::Task["gitlab:db:drop_all_tables"].invoke
pg_env
system('psql', config['database'], '-f', db_file_name)
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index efaefa4ce44..05814fc78f6 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -7,8 +7,8 @@ module Backup
s = {}
s[:db_version] = "#{ActiveRecord::Migrator.current_version}"
s[:backup_created_at] = Time.now
- s[:gitlab_version] = %x{git rev-parse HEAD}.gsub(/\n/,"")
- s[:tar_version] = %x{tar --version | head -1}.gsub(/\n/,"")
+ s[:gitlab_version] = Gitlab::VERSION
+ s[:tar_version] = tar_version
Dir.chdir(Gitlab.config.backup.path)
@@ -87,22 +87,21 @@ module Backup
settings = YAML.load_file("backup_information.yml")
ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
- # backups directory is not always sub of Rails root and able to execute the git rev-parse below
- begin
- Dir.chdir(Rails.root)
-
- # restoring mismatching backups can lead to unexpected problems
- if settings[:gitlab_version] != %x{git rev-parse HEAD}.gsub(/\n/, "")
- puts "GitLab version mismatch:".red
- puts " Your current HEAD differs from the HEAD in the backup!".red
- puts " Please switch to the following revision and try again:".red
- puts " revision: #{settings[:gitlab_version]}".red
- exit 1
- end
- ensure
- # chdir back to original intended dir
- Dir.chdir(Gitlab.config.backup.path)
+ # restoring mismatching backups can lead to unexpected problems
+ if settings[:gitlab_version] != Gitlab::VERSION
+ puts "GitLab version mismatch:".red
+ puts " Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup!".red
+ puts " Please switch to the following version and try again:".red
+ puts " version: #{settings[:gitlab_version]}".red
+ puts
+ puts "Hint: git checkout v#{settings[:gitlab_version]}"
+ exit 1
end
end
+
+ def tar_version
+ tar_version, _ = Gitlab::Popen.popen(%W(tar --version))
+ tar_version.split("\n").first
+ end
end
end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 20fd5ba92a1..552f7eaa5ce 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -72,8 +72,7 @@ module Backup
end
print 'Put GitLab hooks in repositories dirs'.yellow
- gitlab_shell_user_home = File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
- if system("#{gitlab_shell_user_home}/gitlab-shell/support/rewrite-hooks.sh", Gitlab.config.gitlab_shell.repos_path)
+ if system("#{Gitlab.config.gitlab_shell.path}/support/rewrite-hooks.sh", Gitlab.config.gitlab_shell.repos_path)
puts " [DONE]".green
else
puts " [FAILED]".red
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index e79da7e8fd2..e50e1ff4f13 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -3,7 +3,7 @@ module Backup
attr_reader :app_uploads_dir, :backup_uploads_dir, :backup_dir
def initialize
- @app_uploads_dir = Rails.root.join('public', 'uploads')
+ @app_uploads_dir = File.realpath(Rails.root.join('public', 'uploads'))
@backup_dir = Gitlab.config.backup.path
@backup_uploads_dir = File.join(Gitlab.config.backup.path, 'uploads')
end
@@ -21,8 +21,9 @@ module Backup
end
def backup_existing_uploads_dir
+ timestamped_uploads_path = File.join(app_uploads_dir, '..', "uploads.#{Time.now.to_i}")
if File.exists?(app_uploads_dir)
- FileUtils.mv(app_uploads_dir, Rails.root.join('public', "uploads.#{Time.now.to_i}"))
+ FileUtils.mv(app_uploads_dir, timestamped_uploads_path)
end
end
end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 60c03ce1c04..c2f3b851c07 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -1,11 +1,9 @@
require_relative 'shell_env'
-require_relative 'grack_helpers'
module Grack
class Auth < Rack::Auth::Basic
- include Helpers
- attr_accessor :user, :project, :ref, :env
+ attr_accessor :user, :project, :env
def call(env)
@env = env
@@ -24,14 +22,16 @@ module Grack
@env['SCRIPT_NAME'] = ""
- auth!
+ if project
+ auth!
+ else
+ render_not_found
+ end
end
private
def auth!
- return render_not_found unless project
-
if @auth.provided?
return bad_request unless @auth.basic?
@@ -40,12 +40,8 @@ module Grack
# Allow authentication for GitLab CI service
# if valid token passed
- if login == "gitlab-ci-token" && project.gitlab_ci?
- token = project.gitlab_ci_service.token
-
- if token.present? && token == password && service_name == 'git-upload-pack'
- return @app.call(env)
- end
+ if gitlab_ci_request?(login, password)
+ return @app.call(env)
end
@user = authenticate_user(login, password)
@@ -53,23 +49,26 @@ module Grack
if @user
Gitlab::ShellEnv.set_env(@user)
@env['REMOTE_USER'] = @auth.username
- else
- return unauthorized
end
-
- else
- return unauthorized unless project.public?
end
- if authorized_git_request?
+ if authorized_request?
@app.call(env)
else
unauthorized
end
end
- def authorized_git_request?
- authorize_request(service_name)
+ def gitlab_ci_request?(login, password)
+ if login == "gitlab-ci-token" && project.gitlab_ci?
+ token = project.gitlab_ci_service.token
+
+ if token.present? && token == password && git_cmd == 'git-upload-pack'
+ return true
+ end
+ end
+
+ false
end
def authenticate_user(login, password)
@@ -77,31 +76,31 @@ module Grack
auth.find(login, password)
end
- def authorize_request(service)
- case service
- when 'git-upload-pack'
- can?(user, :download_code, project)
- when'git-receive-pack'
- refs.each do |ref|
- action = if project.protected_branch?(ref)
- :push_code_to_protected_branches
- else
- :push_code
- end
-
- return false unless can?(user, action, project)
+ def authorized_request?
+ case git_cmd
+ when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
+ if user
+ Gitlab::GitAccess.new.download_allowed?(user, project)
+ elsif project.public?
+ # Allow clone/fetch for public projects
+ true
+ else
+ false
+ end
+ when *Gitlab::GitAccess::PUSH_COMMANDS
+ if user
+ # Skip user authorization on upload request.
+ # It will be serverd by update hook in repository
+ true
+ else
+ false
end
-
- # Never let git-receive-pack trough unauthenticated; it's
- # harmless but git < 1.8 doesn't like it
- return false if user.nil?
- true
else
false
end
end
- def service_name
+ def git_cmd
if @request.get?
@request.params['service']
elsif @request.post?
@@ -115,28 +114,17 @@ module Grack
@project ||= project_by_path(@request.path_info)
end
- def refs
- @refs ||= parse_refs
- end
-
- def parse_refs
- input = if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
- Zlib::GzipReader.new(@request.body).read
- else
- @request.body.read
- end
-
- # Need to reset seek point
- @request.body.rewind
-
- # Parse refs
- refs = input.force_encoding('ascii-8bit').scan(/refs\/heads\/([\/\w\.-]+)/n).flatten.compact
+ def project_by_path(path)
+ if m = /^([\w\.\/-]+)\.git/.match(path).to_a
+ path_with_namespace = m.last
+ path_with_namespace.gsub!(/\.wiki$/, '')
- # Cleanup grabare from refs
- # if push to multiple branches
- refs.map do |ref|
- ref.gsub(/00.*/, "")
+ Project.find_with_namespace(path_with_namespace)
end
end
+
+ def render_not_found
+ [404, {"Content-Type" => "text/plain"}, ["Not Found"]]
+ end
end
end
diff --git a/lib/gitlab/backend/grack_helpers.rb b/lib/gitlab/backend/grack_helpers.rb
deleted file mode 100644
index cb747fe0137..00000000000
--- a/lib/gitlab/backend/grack_helpers.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module Grack
- module Helpers
- def project_by_path(path)
- if m = /^([\w\.\/-]+)\.git/.match(path).to_a
- path_with_namespace = m.last
- path_with_namespace.gsub!(/\.wiki$/, '')
-
- Project.find_with_namespace(path_with_namespace)
- end
- end
-
- def render_not_found
- [404, {"Content-Type" => "text/plain"}, ["Not Found"]]
- end
-
- def can?(object, action, subject)
- abilities.allowed?(object, action, subject)
- end
-
- def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << Ability
- abilities
- end
- end
- end
-end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 7121c8e40d2..b93800e235f 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -2,6 +2,12 @@ module Gitlab
class Shell
class AccessDenied < StandardError; end
+ class KeyAdder < Struct.new(:io)
+ def add_key(id, key)
+ io.puts("#{id}\t#{key.strip}")
+ end
+ end
+
# Init new repository
#
# name - project path with namespace
@@ -130,6 +136,16 @@ module Gitlab
system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content
end
+ # Batch-add keys to authorized_keys
+ #
+ # Ex.
+ # batch_add_keys { |adder| adder.add_key("key-42", "sha-rsa ...") }
+ def batch_add_keys(&block)
+ IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys batch-add-keys), 'w') do |io|
+ block.call(KeyAdder.new(io))
+ end
+ end
+
# Remove ssh key from gitlab shell
#
# Ex.
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
new file mode 100644
index 00000000000..1ab8f9213a3
--- /dev/null
+++ b/lib/gitlab/git_access.rb
@@ -0,0 +1,74 @@
+module Gitlab
+ class GitAccess
+ DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
+ PUSH_COMMANDS = %w{ git-receive-pack }
+
+ attr_reader :params, :project, :git_cmd, :user
+
+ def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil)
+ case cmd
+ when *DOWNLOAD_COMMANDS
+ if actor.is_a? User
+ download_allowed?(actor, project)
+ elsif actor.is_a? DeployKey
+ actor.projects.include?(project)
+ elsif actor.is_a? Key
+ download_allowed?(actor.user, project)
+ else
+ raise 'Wrong actor'
+ end
+ when *PUSH_COMMANDS
+ if actor.is_a? User
+ push_allowed?(actor, project, ref, oldrev, newrev)
+ elsif actor.is_a? DeployKey
+ # Deploy key not allowed to push
+ return false
+ elsif actor.is_a? Key
+ push_allowed?(actor.user, project, ref, oldrev, newrev)
+ else
+ raise 'Wrong actor'
+ end
+ else
+ false
+ end
+ end
+
+ def download_allowed?(user, project)
+ if user && user_allowed?(user)
+ user.can?(:download_code, project)
+ else
+ false
+ end
+ end
+
+ def push_allowed?(user, project, ref, oldrev, newrev)
+ if user && user_allowed?(user)
+ action = if project.protected_branch?(ref)
+ :push_code_to_protected_branches
+ else
+ :push_code
+ end
+ user.can?(action, project)
+ else
+ false
+ end
+ end
+
+ private
+
+ def user_allowed?(user)
+ return false if user.blocked?
+
+ if Gitlab.config.ldap.enabled
+ if user.ldap_user?
+ # Check if LDAP user exists and match LDAP user_filter
+ unless Gitlab::LDAP::Access.new.allowed?(user)
+ return false
+ end
+ end
+ end
+
+ true
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
new file mode 100644
index 00000000000..8f492e5c012
--- /dev/null
+++ b/lib/gitlab/ldap/access.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ module LDAP
+ class Access
+ attr_reader :adapter
+
+ def self.open(&block)
+ Gitlab::LDAP::Adapter.open do |adapter|
+ block.call(self.new(adapter))
+ end
+ end
+
+ def initialize(adapter=nil)
+ @adapter = adapter
+ end
+
+ def allowed?(user)
+ !!Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
+ rescue
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
new file mode 100644
index 00000000000..983a2956a35
--- /dev/null
+++ b/lib/gitlab/ldap/adapter.rb
@@ -0,0 +1,86 @@
+module Gitlab
+ module LDAP
+ class Adapter
+ attr_reader :ldap
+
+ def self.open(&block)
+ Net::LDAP.open(adapter_options) do |ldap|
+ block.call(self.new(ldap))
+ end
+ end
+
+ def self.config
+ Gitlab.config.ldap
+ end
+
+ def self.adapter_options
+ encryption = config['method'].to_s == 'ssl' ? :simple_tls : nil
+
+ options = {
+ host: config['host'],
+ port: config['port'],
+ encryption: encryption
+ }
+
+ auth_options = {
+ auth: {
+ method: :simple,
+ username: config['bind_dn'],
+ password: config['password']
+ }
+ }
+
+ if config['password'] || config['bind_dn']
+ options.merge!(auth_options)
+ end
+ options
+ end
+
+
+ def initialize(ldap=nil)
+ @ldap = ldap || Net::LDAP.new(self.class.adapter_options)
+ end
+
+ def users(field, value)
+ if field.to_sym == :dn
+ options = {
+ base: value
+ }
+ else
+ options = {
+ base: config['base'],
+ filter: Net::LDAP::Filter.eq(field, value)
+ }
+ end
+
+ if config['user_filter'].present?
+ user_filter = Net::LDAP::Filter.construct(config['user_filter'])
+
+ options[:filter] = if options[:filter]
+ Net::LDAP::Filter.join(options[:filter], user_filter)
+ else
+ user_filter
+ end
+ end
+
+ entries = ldap.search(options).select do |entry|
+ entry.respond_to? config.uid
+ end
+
+ entries.map do |entry|
+ Gitlab::LDAP::Person.new(entry)
+ end
+ end
+
+ def user(*args)
+ users(*args).first
+ end
+
+ private
+
+ def config
+ @config ||= self.class.config
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb
new file mode 100644
index 00000000000..06b17c58f8c
--- /dev/null
+++ b/lib/gitlab/ldap/person.rb
@@ -0,0 +1,50 @@
+module Gitlab
+ module LDAP
+ class Person
+ def self.find_by_uid(uid, adapter=nil)
+ adapter ||= Gitlab::LDAP::Adapter.new
+ adapter.user(config.uid, uid)
+ end
+
+ def self.find_by_dn(dn, adapter=nil)
+ adapter ||= Gitlab::LDAP::Adapter.new
+ adapter.user('dn', dn)
+ end
+
+ def initialize(entry)
+ Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
+ @entry = entry
+ end
+
+ def name
+ entry.cn.first
+ end
+
+ def uid
+ entry.send(config.uid).first
+ end
+
+ def username
+ uid
+ end
+
+ def dn
+ entry.dn
+ end
+
+ private
+
+ def entry
+ @entry
+ end
+
+ def adapter
+ @adapter ||= Gitlab::LDAP::Adapter.new
+ end
+
+ def config
+ @config ||= Gitlab.config.ldap
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 4ba69525f85..01d86430f02 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -13,8 +13,8 @@ module Gitlab
def find_or_create(auth)
@auth = auth
- if uid.blank? || email.blank?
- raise_error("Account must provide an uid and email address")
+ if uid.blank? || email.blank? || username.blank?
+ raise_error("Account must provide a dn, uid and email address")
end
user = find(auth)
@@ -62,8 +62,16 @@ module Gitlab
return nil unless ldap_conf.enabled && login.present? && password.present?
ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf)
+ filter = Net::LDAP::Filter.eq(ldap.uid, login)
+
+ # Apply LDAP user filter if present
+ if ldap_conf['user_filter'].present?
+ user_filter = Net::LDAP::Filter.construct(ldap_conf['user_filter'])
+ filter = Net::LDAP::Filter.join(filter, user_filter)
+ end
+
ldap_user = ldap.bind_as(
- filter: Net::LDAP::Filter.eq(ldap.uid, login),
+ filter: filter,
size: 1,
password: password
)
@@ -71,16 +79,6 @@ module Gitlab
find_by_uid(ldap_user.dn) if ldap_user
end
- # Check LDAP user existance by dn. User in git over ssh check
- #
- # It covers 2 cases:
- # * when ldap account was removed
- # * when ldap account was deactivated by change of OU membership in 'dn'
- def blocked?(dn)
- ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf)
- ldap.connection.search(base: dn, scope: Net::LDAP::SearchScope_BaseObject, size: 1).blank?
- end
-
private
def find_by_uid_and_provider
@@ -92,6 +90,10 @@ module Gitlab
model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last
end
+ def username
+ auth.info.nickname.to_s.force_encoding("utf-8")
+ end
+
def provider
'ldap'
end
diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb
index 389eef3395f..64cf3303ea3 100644
--- a/lib/gitlab/logger.rb
+++ b/lib/gitlab/logger.rb
@@ -11,12 +11,14 @@ module Gitlab
def self.read_latest
path = Rails.root.join("log", file_name)
self.build unless File.exist?(path)
- logs = `tail -n 2000 #{path}`.split("\n")
+ tail_output, _ = Gitlab::Popen.popen(%W(tail -n 2000 #{path}))
+ tail_output.split("\n")
end
def self.read_latest_for filename
path = Rails.root.join("log", filename)
- logs = `tail -n 2000 #{path}`.split("\n")
+ tail_output, _ = Gitlab::Popen.popen(%W(tail -n 2000 #{path}))
+ tail_output.split("\n")
end
def self.build
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index dc9c0e0ab2c..de14a3eca27 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -98,7 +98,7 @@ module Gitlab
(?<prefix>\W)? # Prefix
( # Reference
@(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
- |\#(?<issue>([a-zA-Z]+-)?\d+) # Issue ID
+ |\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID
|!(?<merge_request>\d+) # MR ID
|\$(?<snippet>\d+) # Snippet ID
|(?<commit>[\h]{6,40}) # Commit ID
@@ -152,7 +152,7 @@ module Gitlab
#
# Returns boolean
def valid_emoji?(emoji)
- Emoji.names.include? emoji
+ Emoji.find_by_name emoji
end
# Private: Dispatches to a dedicated processing method based on reference
@@ -166,8 +166,8 @@ module Gitlab
end
def reference_user(identifier)
- if member = @project.team_members.find { |user| user.username == identifier }
- link_to("@#{identifier}", user_url(identifier), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member
+ if user = User.find_by_username(identifier)
+ link_to("@#{identifier}", user_url(identifier), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}"))
end
end
diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb
index 5283cf0b821..e2fbafb3899 100644
--- a/lib/gitlab/popen.rb
+++ b/lib/gitlab/popen.rb
@@ -1,8 +1,16 @@
require 'fileutils'
+require 'open3'
module Gitlab
module Popen
- def popen(cmd, path)
+ extend self
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ raise "System commands must be given as an array of strings"
+ end
+
+ path ||= Dir.pwd
vars = { "PWD" => path }
options = { chdir: path }
@@ -12,10 +20,10 @@ module Gitlab
@cmd_output = ""
@cmd_status = 0
- Open3.popen3(vars, cmd, options) do |stdin, stdout, stderr, wait_thr|
- @cmd_status = wait_thr.value.exitstatus
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
@cmd_output << stdout.read
@cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
end
return @cmd_output, @cmd_status
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index d18fc8bf2ce..e932b64f4f0 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -7,7 +7,7 @@ module Gitlab
end
def project_name_regex
- /\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z/
+ /\A[a-zA-Z0-9_][a-zA-Z0-9_\-\. ]*\z/
end
def name_regex
@@ -49,7 +49,7 @@ module Gitlab
protected
def default_regex
- /\A[.?]?[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z/
+ /\A[.?]?[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*(?<!\.git)\z/
end
end
end
diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb
index 353c3024aad..bdfcf254e9e 100644
--- a/lib/gitlab/satellite/satellite.rb
+++ b/lib/gitlab/satellite/satellite.rb
@@ -1,5 +1,9 @@
module Gitlab
- class SatelliteNotExistError < StandardError; end
+ class SatelliteNotExistError < StandardError
+ def initialize(msg = "Satellite doesn't exist")
+ super
+ end
+ end
module Satellite
class Satellite
@@ -17,14 +21,9 @@ module Gitlab
Gitlab::Satellite::Logger.error(message)
end
- def raise_no_satellite
- raise SatelliteNotExistError.new("Satellite doesn't exist")
- end
-
def clear_and_update!
- raise_no_satellite unless exists?
+ raise SatelliteNotExistError unless exists?
- File.exists? path
@repo = nil
clear_working_dir!
delete_heads!
@@ -33,7 +32,7 @@ module Gitlab
end
def create
- output, status = popen("git clone #{project.repository.path_to_repo} #{path}",
+ output, status = popen(%W(git clone -- #{project.repository.path_to_repo} #{path}),
Gitlab.config.satellites.path)
log("PID: #{project.id}: git clone #{project.repository.path_to_repo} #{path}")
@@ -55,7 +54,7 @@ module Gitlab
# * Changes the current directory to the satellite's working dir
# * Yields
def lock
- raise_no_satellite unless exists?
+ raise SatelliteNotExistError unless exists?
File.open(lock_file, "w+") do |f|
begin
@@ -77,7 +76,7 @@ module Gitlab
end
def repo
- raise_no_satellite unless exists?
+ raise SatelliteNotExistError unless exists?
@repo ||= Grit::Repo.new(path)
end
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 3aa3b2ba1e9..39de1223b18 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -1,10 +1,29 @@
module Gitlab
class Seeder
def self.quiet
+ mute_mailer
SeedFu.quiet = true
yield
SeedFu.quiet = false
puts "\nOK".green
end
+
+ def self.by_user(user)
+ begin
+ Thread.current[:current_user] = user
+ yield
+ ensure
+ Thread.current[:current_user] = nil
+ end
+ end
+
+ def self.mute_mailer
+ code = <<-eos
+def Notify.delay
+ self
+end
+ eos
+ eval(code)
+ end
end
end
diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb
index f46685e4bbe..0846359f9b1 100644
--- a/lib/gitlab/upgrader.rb
+++ b/lib/gitlab/upgrader.rb
@@ -1,3 +1,4 @@
+require_relative "popen"
require_relative "version_info"
module Gitlab
@@ -42,28 +43,33 @@ module Gitlab
end
def latest_version_raw
- git_tags = `git ls-remote --tags origin | grep tags\/v#{current_version.major}`
- git_tags = git_tags.lines.to_a.select { |version| version =~ /v\d\.\d\.\d\Z/ }
+ remote_tags, _ = Gitlab::Popen.popen(%W(git ls-remote --tags origin))
+ git_tags = remote_tags.split("\n").grep(/tags\/v#{current_version.major}/)
+ git_tags = git_tags.select { |version| version =~ /v\d\.\d\.\d\Z/ }
last_tag = git_tags.last.match(/v\d\.\d\.\d/).to_s
end
def update_commands
{
- "Stash changed files" => "git stash",
- "Get latest code" => "git fetch",
- "Switch to new version" => "git checkout v#{latest_version}",
- "Install gems" => "bundle",
- "Migrate DB" => "bundle exec rake db:migrate RAILS_ENV=production",
- "Recompile assets" => "bundle exec rake assets:clean assets:precompile RAILS_ENV=production",
- "Clear cache" => "bundle exec rake cache:clear RAILS_ENV=production"
+ "Stash changed files" => %W(git stash),
+ "Get latest code" => %W(git fetch),
+ "Switch to new version" => %W(git checkout v#{latest_version}),
+ "Install gems" => %W(bundle),
+ "Migrate DB" => %W(bundle exec rake db:migrate),
+ "Recompile assets" => %W(bundle exec rake assets:clean assets:precompile),
+ "Clear cache" => %W(bundle exec rake cache:clear)
}
end
+ def env
+ {'RAILS_ENV' => 'production'}
+ end
+
def upgrade
update_commands.each do |title, cmd|
puts title
- puts " -> #{cmd}"
- if system(cmd)
+ puts " -> #{cmd.join(' ')}"
+ if system(env, *cmd)
puts " -> OK"
else
puts " -> FAILED"
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index 42f6316910a..86d8b69b0ef 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -24,7 +24,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
<div class="highlighted-data #{h.user_color_scheme_class}">
<div class="highlight">
- <pre><code class="#{language}">#{code}</code></pre>
+ <pre><code class="#{language}">#{h.send(:html_escape, code)}</code></pre>
</div>
</div>
@@ -46,8 +46,10 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
end
def preprocess(full_document)
- if @project
- h.create_relative_links(full_document, @project, @ref, @request_path, is_wiki?)
+ if is_wiki?
+ full_document
+ elsif @project
+ h.create_relative_links(full_document, @project, @ref, @request_path)
else
full_document
end
diff --git a/lib/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh
index b96f73058b6..4684957233a 100755
--- a/lib/support/deploy/deploy.sh
+++ b/lib/support/deploy/deploy.sh
@@ -28,7 +28,7 @@ sudo -u git -H git pull origin master
echo 'Deploy: Bundle and migrate'
# change it to your needs
-sudo -u git -H bundle --without aws development test postgres --deployment
+sudo -u git -H bundle --without aws development test mysql --deployment
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
@@ -42,4 +42,4 @@ echo 'Deploy: Starting GitLab server...'
sudo service gitlab start
sudo -u git -H rm /home/git/gitlab/public/index.html
-echo 'Deploy: Done' \ No newline at end of file
+echo 'Deploy: Done'
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index c6e570784e0..3dd4465a6d8 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -40,7 +40,7 @@ test -f /etc/default/gitlab && . /etc/default/gitlab
# Switch to the app_user if it is not he/she who is running the script.
if [ "$USER" != "$app_user" ]; then
- sudo -u "$app_user" -H -i $0 "$@"; exit;
+ eval su - "$app_user" -c $(echo \")$0 "$@"$(echo \"); exit;
fi
# Switch to the gitlab path, exit on failure.
@@ -131,7 +131,7 @@ check_stale_pids(){
fi
fi
if [ "$spid" != "0" -a "$sidekiq_status" != "0" ]; then
- echo "Removing stale Sidekiq web server pid. This is most likely caused by the Sidekiq crashing the last time it ran."
+ echo "Removing stale Sidekiq job dispatcher pid. This is most likely caused by Sidekiq crashing the last time it ran."
if ! rm "$sidekiq_pid_path"; then
echo "Unable to remove stale pid, exiting"
exit 1
@@ -149,15 +149,15 @@ exit_if_not_running(){
}
## Starts Unicorn and Sidekiq if they're not running.
-start() {
+start_gitlab() {
check_stale_pids
if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then
echo -n "Starting both the GitLab Unicorn and Sidekiq"
elif [ "$web_status" != "0" ]; then
- echo -n "Starting GitLab Sidekiq"
- elif [ "$sidekiq_status" != "0" ]; then
echo -n "Starting GitLab Unicorn"
+ elif [ "$sidekiq_status" != "0" ]; then
+ echo -n "Starting GitLab Sidekiq"
fi
# Then check if the service is running. If it is: don't start again.
@@ -167,7 +167,7 @@ start() {
# Remove old socket if it exists
rm -f "$socket_path"/gitlab.socket 2>/dev/null
# Start the web server
- RAILS_ENV=$RAILS_ENV script/web start &
+ RAILS_ENV=$RAILS_ENV script/web start
fi
# If sidekiq is already running, don't start it again.
@@ -184,15 +184,15 @@ start() {
}
## Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them.
-stop() {
+stop_gitlab() {
exit_if_not_running
if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then
echo -n "Shutting down both Unicorn and Sidekiq"
elif [ "$web_status" = "0" ]; then
- echo -n "Shutting down Sidekiq"
- elif [ "$sidekiq_status" = "0" ]; then
echo -n "Shutting down Unicorn"
+ elif [ "$sidekiq_status" = "0" ]; then
+ echo -n "Shutting down Sidekiq"
fi
# If the Unicorn web server is running, tell it to stop;
@@ -246,7 +246,7 @@ print_status() {
}
## Tells unicorn to reload it's config and Sidekiq to restart
-reload(){
+reload_gitlab(){
exit_if_not_running
if [ "$wpid" = "0" ];then
echo "The GitLab Unicorn Web server is not running thus its configuration can't be reloaded."
@@ -263,12 +263,12 @@ reload(){
}
## Restarts Sidekiq and Unicorn.
-restart(){
+restart_gitlab(){
check_status
if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then
- stop
+ stop_gitlab
fi
- start
+ start_gitlab
}
@@ -276,16 +276,16 @@ restart(){
case "$1" in
start)
- start
+ start_gitlab
;;
stop)
- stop
+ stop_gitlab
;;
restart)
- restart
+ restart_gitlab
;;
reload|force-reload)
- reload
+ reload_gitlab
;;
status)
print_status
diff --git a/lib/support/logrotate/gitlab b/lib/support/logrotate/gitlab
index df9398d0795..d9b07b61ec3 100644
--- a/lib/support/logrotate/gitlab
+++ b/lib/support/logrotate/gitlab
@@ -2,21 +2,19 @@
# based on: http://stackoverflow.com/a/4883967
/home/git/gitlab/log/*.log {
- weekly
+ daily
missingok
- rotate 52
+ rotate 90
compress
- delaycompress
notifempty
copytruncate
}
/home/git/gitlab-shell/gitlab-shell.log {
- weekly
+ daily
missingok
- rotate 52
+ rotate 90
compress
- delaycompress
notifempty
copytruncate
}
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 882f0386046..5bff362da0e 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -27,7 +27,7 @@ server {
# Increase this if you want to upload large attachments
# Or if you want to accept large git objects over http
- client_max_body_size 5m;
+ client_max_body_size 20m;
# individual nginx logs for this gitlab vhost
access_log /var/log/nginx/gitlab_access.log;
@@ -54,6 +54,14 @@ server {
proxy_pass http://gitlab;
}
+ # Enable gzip compression as per rails guide: http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression
+ location ~ ^/(assets)/ {
+ root /home/git/gitlab/public;
+ gzip_static on; # to serve pre-gzipped version
+ expires max;
+ add_header Cache-Control public;
+ }
+
error_page 502 /502.html;
}
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
index 7d3602211c1..058c7417040 100644
--- a/lib/tasks/dev.rake
+++ b/lib/tasks/dev.rake
@@ -1,10 +1,10 @@
+task dev: ["dev:setup"]
+
namespace :dev do
desc "GITLAB | Setup developer environment (db, fixtures)"
task :setup => :environment do
ENV['force'] = 'yes'
- Rake::Task["db:setup"].invoke
- Rake::Task["db:seed_fu"].invoke
+ Rake::Task["gitlab:setup"].invoke
Rake::Task["gitlab:shell:setup"].invoke
end
end
-
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index c91dedf74c7..3b9b2531bf7 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -17,6 +17,7 @@ namespace :gitlab do
check_database_config_exists
check_database_is_not_sqlite
check_migrations_are_up
+ check_orphaned_users_groups
check_gitlab_config_exists
check_gitlab_config_not_outdated
check_log_writable
@@ -65,6 +66,7 @@ namespace :gitlab do
puts "no".green
else
puts "yes".red
+ puts "Please fix this by removing the SQLite entry from the database.yml".blue
for_more_information(
"https://github.com/gitlabhq/gitlabhq/wiki/Migrate-from-SQLite-to-MySQL",
see_database_guide
@@ -168,7 +170,7 @@ namespace :gitlab do
def check_migrations_are_up
print "All migrations up? ... "
- migration_status = `bundle exec rake db:migrate:status`
+ migration_status, _ = Gitlab::Popen.popen(%W(bundle exec rake db:migrate:status))
unless migration_status =~ /down\s+\d{14}/
puts "yes".green
@@ -181,6 +183,19 @@ namespace :gitlab do
end
end
+ def check_orphaned_users_groups
+ print "Database contains orphaned UsersGroups? ... "
+ if UsersGroup.where("user_id not in (select id from users)").count > 0
+ puts "yes".red
+ try_fixing_it(
+ "You can delete the orphaned records using something along the lines of:",
+ sudo_gitlab("bundle exec rails runner -e production 'UsersGroup.where(\"user_id NOT IN (SELECT id FROM users)\").delete_all'")
+ )
+ else
+ puts "no".green
+ end
+ end
+
def check_satellites_exist
print "Projects have satellites? ... "
@@ -255,7 +270,7 @@ namespace :gitlab do
def check_redis_version
print "Redis version >= 2.0.0? ... "
- if run_and_match("redis-cli --version", /redis-cli 2.\d.\d/)
+ if run_and_match(%W(redis-cli --version), /redis-cli 2.\d.\d/)
puts "yes".green
else
puts "no".red
@@ -295,7 +310,7 @@ namespace :gitlab do
"user.email" => Gitlab.config.gitlab.email_from
}
correct_options = options.map do |name, value|
- run("git config --global --get #{name}").try(:squish) == value
+ run(%W(git config --global --get #{name})).try(:squish) == value
end
if correct_options.all?
@@ -489,7 +504,7 @@ namespace :gitlab do
"sudo -u #{gitlab_shell_ssh_user} ln -sf #{gitlab_shell_hook_file} #{project_hook_file}"
)
for_more_information(
- "#{gitlab_shell_user_home}/gitlab-shell/support/rewrite-hooks.sh"
+ "#{gitlab_shell_path}/support/rewrite-hooks.sh"
)
fix_and_rerun
next
@@ -513,7 +528,7 @@ namespace :gitlab do
end
def check_gitlab_shell_self_test
- gitlab_shell_repo_base = File.expand_path('gitlab-shell', gitlab_shell_user_home)
+ gitlab_shell_repo_base = gitlab_shell_path
check_cmd = File.expand_path('bin/check', gitlab_shell_repo_base)
puts "Running #{check_cmd}"
if system(check_cmd, chdir: gitlab_shell_repo_base)
@@ -559,8 +574,8 @@ namespace :gitlab do
# Helper methods
########################
- def gitlab_shell_user_home
- File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
+ def gitlab_shell_path
+ Gitlab.config.gitlab_shell.path
end
def gitlab_shell_version
@@ -628,7 +643,8 @@ namespace :gitlab do
end
def sidekiq_process_count
- `ps ux`.scan(/sidekiq \d+\.\d+\.\d+/).count
+ ps_ux, _ = Gitlab::Popen.popen(%W(ps ux))
+ ps_ux.scan(/sidekiq \d+\.\d+\.\d+/).count
end
end
@@ -661,7 +677,20 @@ namespace :gitlab do
end
def filter
- Net::LDAP::Filter.present?(ldap_config.uid)
+ uid_filter = Net::LDAP::Filter.present?(ldap_config.uid)
+ if user_filter
+ Net::LDAP::Filter.join(uid_filter, user_filter)
+ else
+ uid_filter
+ end
+ end
+
+ def user_filter
+ if ldap_config['user_filter'] && ldap_config.user_filter.present?
+ Net::LDAP::Filter.construct(ldap_config.user_filter)
+ else
+ nil
+ end
end
def ldap
@@ -726,7 +755,7 @@ namespace :gitlab do
end
def check_gitlab_shell
- required_version = Gitlab::VersionInfo.new(1, 7, 9)
+ required_version = Gitlab::VersionInfo.new(1, 9, 1)
current_version = Gitlab::VersionInfo.parse(gitlab_shell_version)
print "GitLab Shell version >= #{required_version} ? ... "
@@ -739,7 +768,7 @@ namespace :gitlab do
def check_git_version
required_version = Gitlab::VersionInfo.new(1, 7, 10)
- current_version = Gitlab::VersionInfo.parse(run("#{Gitlab.config.git.bin_path} --version"))
+ current_version = Gitlab::VersionInfo.parse(run(%W(#{Gitlab.config.git.bin_path} --version)))
puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\""
print "Git version >= #{required_version} ? ... "
diff --git a/lib/tasks/gitlab/db/drop_all_tables.rake b/lib/tasks/gitlab/db/drop_all_tables.rake
new file mode 100644
index 00000000000..a66030ab93a
--- /dev/null
+++ b/lib/tasks/gitlab/db/drop_all_tables.rake
@@ -0,0 +1,10 @@
+namespace :gitlab do
+ namespace :db do
+ task drop_all_tables: :environment do
+ connection = ActiveRecord::Base.connection
+ connection.tables.each do |table|
+ connection.drop_table(table)
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/generate_docs.rake b/lib/tasks/gitlab/generate_docs.rake
index 58795fac4af..332cd61f84c 100644
--- a/lib/tasks/gitlab/generate_docs.rake
+++ b/lib/tasks/gitlab/generate_docs.rake
@@ -1,7 +1,7 @@
namespace :gitlab do
desc "GITLAB | Generate sdocs for project"
task generate_docs: :environment do
- system("bundle exec sdoc -o doc/code app lib")
+ system(*%W(bundle exec sdoc -o doc/code app lib))
end
end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index ea83efcd887..690f414f3b3 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -4,20 +4,20 @@ namespace :gitlab do
task info: :environment do
# check if there is an RVM environment
- rvm_version = run_and_match("rvm --version", /[\d\.]+/).try(:to_s)
+ rvm_version = run_and_match(%W(rvm --version), /[\d\.]+/).try(:to_s)
# check Ruby version
- ruby_version = run_and_match("ruby --version", /[\d\.p]+/).try(:to_s)
+ ruby_version = run_and_match(%W(ruby --version), /[\d\.p]+/).try(:to_s)
# check Gem version
- gem_version = run("gem --version")
+ gem_version = run(%W(gem --version))
# check Bundler version
- bunder_version = run_and_match("bundle --version", /[\d\.]+/).try(:to_s)
+ bunder_version = run_and_match(%W(bundle --version), /[\d\.]+/).try(:to_s)
# check Bundler version
- rake_version = run_and_match("rake --version", /[\d\.]+/).try(:to_s)
+ rake_version = run_and_match(%W(rake --version), /[\d\.]+/).try(:to_s)
puts ""
puts "System information".yellow
puts "System:\t\t#{os_name || "unknown".red}"
- puts "Current User:\t#{`whoami`}"
+ puts "Current User:\t#{run(%W(whoami))}"
puts "Using RVM:\t#{rvm_version.present? ? "yes".green : "no"}"
puts "RVM Version:\t#{rvm_version}" if rvm_version.present?
puts "Ruby Version:\t#{ruby_version || "unknown".red}"
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index 2b730774e06..853994dd67d 100644
--- a/lib/tasks/gitlab/setup.rake
+++ b/lib/tasks/gitlab/setup.rake
@@ -15,6 +15,14 @@ namespace :gitlab do
end
Rake::Task["db:setup"].invoke
+
+ config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env]
+ success = case config["adapter"]
+ when /^mysql/ then
+ Rake::Task["add_limits_mysql"].invoke
+ when "postgresql" then
+ end
+
Rake::Task["db:seed_fu"].invoke
rescue Gitlab::TaskAbortedByUserError
puts "Quitting...".red
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 0d7a390bc92..08de0f2dd5d 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -34,14 +34,18 @@ namespace :gitlab do
Gitlab::Shell.new.remove_all_keys
- Key.find_each(batch_size: 1000) do |key|
- if Gitlab::Shell.new.add_key(key.shell_id, key.key)
+ Gitlab::Shell.new.batch_add_keys do |adder|
+ Key.find_each(batch_size: 1000) do |key|
+ adder.add_key(key.shell_id, key.key)
print '.'
- else
- print 'F'
end
end
+ unless $?.success?
+ puts "Failed to add keys...".red
+ exit 1
+ end
+
rescue Gitlab::TaskAbortedByUserError
puts "Quitting...".red
exit 1
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index c46d5855faf..da61c6e007f 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -28,7 +28,7 @@ namespace :gitlab do
# It will primarily use lsb_relase to determine the OS.
# It has fallbacks to Debian, SuSE, OS X and systems running systemd.
def os_name
- os_name = run("lsb_release -irs")
+ os_name = run(%W(lsb_release -irs))
os_name ||= if File.readable?('/etc/system-release')
File.read('/etc/system-release')
end
@@ -39,7 +39,7 @@ namespace :gitlab do
os_name ||= if File.readable?('/etc/SuSE-release')
File.read('/etc/SuSE-release')
end
- os_name ||= if os_x_version = run("sw_vers -productVersion")
+ os_name ||= if os_x_version = run(%W(sw_vers -productVersion))
"Mac OS X #{os_x_version}"
end
os_name ||= if File.readable?('/etc/os-release')
@@ -80,13 +80,14 @@ namespace :gitlab do
#
# see also #run_and_match
def run(command)
- unless `#{command} 2>/dev/null`.blank?
- `#{command}`
- end
+ output, _ = Gitlab::Popen.popen(command)
+ output
+ rescue Errno::ENOENT
+ '' # if the command does not exist, return an empty string
end
def uid_for(user_name)
- run("id -u #{user_name}").chomp.to_i
+ run(%W(id -u #{user_name})).chomp.to_i
end
def gid_for(group_name)
@@ -100,7 +101,7 @@ namespace :gitlab do
def warn_user_is_not_gitlab
unless @warned_user_not_gitlab
gitlab_user = Gitlab.config.gitlab.user
- current_user = run("whoami").chomp
+ current_user = run(%W(whoami)).chomp
unless current_user == gitlab_user
puts "#{Colored.color(:black)+Colored.color(:on_yellow)} Warning #{Colored.extra(:clear)}"
puts " You are running as user #{current_user.magenta}, we hope you know what you are doing."
diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake
index 011748c9711..2c9b9978933 100644
--- a/lib/tasks/gitlab/test.rake
+++ b/lib/tasks/gitlab/test.rake
@@ -2,15 +2,13 @@ namespace :gitlab do
desc "GITLAB | Run all tests"
task :test do
cmds = [
- "rake db:setup",
- "rake db:seed_fu",
- "rake spinach",
- "rake spec",
- "rake jasmine:ci"
+ %W(rake spinach),
+ %W(rake spec),
+ %W(rake jasmine:ci)
]
cmds.each do |cmd|
- system(cmd + " RAILS_ENV=test")
+ system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd)
raise "#{cmd} failed!" unless $?.exitstatus.zero?
end
diff --git a/lib/tasks/migrate/add_limits_mysql.rake b/lib/tasks/migrate/add_limits_mysql.rake
new file mode 100644
index 00000000000..46b6451752b
--- /dev/null
+++ b/lib/tasks/migrate/add_limits_mysql.rake
@@ -0,0 +1,14 @@
+desc "GITLAB | Add limits to strings in mysql database"
+task add_limits_mysql: :environment do
+ puts "Adding limits to schema.rb for mysql"
+ LimitsToMysql.new.up
+end
+
+class LimitsToMysql < ActiveRecord::Migration
+ def up
+ change_column :merge_request_diffs, :st_commits, :text, limit: 2147483647
+ change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
+ change_column :snippets, :content, :text, limit: 2147483647
+ change_column :notes, :st_diff, :text, limit: 2147483647
+ end
+end
diff --git a/lib/tasks/sidekiq.rake b/lib/tasks/sidekiq.rake
index e91678473a8..ba806e53ccf 100644
--- a/lib/tasks/sidekiq.rake
+++ b/lib/tasks/sidekiq.rake
@@ -1,21 +1,21 @@
namespace :sidekiq do
desc "GITLAB | Stop sidekiq"
task :stop do
- system "script/background_jobs stop"
+ system *%W(script/background_jobs stop)
end
desc "GITLAB | Start sidekiq"
task :start do
- system "script/background_jobs start"
+ system *%W(script/background_jobs start)
end
desc 'GitLab | Restart sidekiq'
task :restart do
- system "script/background_jobs restart"
+ system *%W(script/background_jobs restart)
end
desc "GITLAB | Start sidekiq with launchd on Mac OS X"
task :launchd do
- system "script/background_jobs start_no_deamonize"
+ system *%W(script/background_jobs start_no_deamonize)
end
end
diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake
new file mode 100644
index 00000000000..90a1809914b
--- /dev/null
+++ b/lib/tasks/spec.rake
@@ -0,0 +1,14 @@
+Rake::Task["spec"].clear if Rake::Task.task_defined?('spec')
+
+desc "GITLAB | Run specs"
+task :spec do
+ cmds = [
+ %W(rake gitlab:setup),
+ %W(rspec spec),
+ ]
+
+ cmds.each do |cmd|
+ system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd)
+ raise "#{cmd} failed!" unless $?.exitstatus.zero?
+ end
+end
diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake
new file mode 100644
index 00000000000..c23d0e0e188
--- /dev/null
+++ b/lib/tasks/spinach.rake
@@ -0,0 +1,14 @@
+Rake::Task["spinach"].clear if Rake::Task.task_defined?('spinach')
+
+desc "GITLAB | Run spinach"
+task :spinach do
+ cmds = [
+ %W(rake gitlab:setup),
+ %W(spinach),
+ ]
+
+ cmds.each do |cmd|
+ system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd)
+ raise "#{cmd} failed!" unless $?.exitstatus.zero?
+ end
+end
diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake
new file mode 100644
index 00000000000..f19da1bb437
--- /dev/null
+++ b/lib/tasks/test.rake
@@ -0,0 +1,6 @@
+Rake::Task["test"].clear
+
+desc "GITLAB | Run all tests"
+task :test do
+ Rake::Task["gitlab:test"].invoke
+end
diff --git a/script/background_jobs b/script/background_jobs
index 623e26a2831..52732f5532b 100755
--- a/script/background_jobs
+++ b/script/background_jobs
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
cd $(dirname $0)/..
app_root=$(pwd)
@@ -6,6 +6,11 @@ sidekiq_pidfile="$app_root/tmp/pids/sidekiq.pid"
sidekiq_logfile="$app_root/log/sidekiq.log"
gitlab_user=$(ls -l config.ru | awk '{print $3}')
+function warn
+{
+ echo "$@" 1>&2
+}
+
function stop
{
bundle exec sidekiqctl stop $sidekiq_pidfile >> $sidekiq_logfile 2>&1
@@ -35,6 +40,22 @@ function start_sidekiq
bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
}
+function load_ok
+{
+ sidekiq_pid=$(cat $sidekiq_pidfile)
+ if [[ -z $sidekiq_pid ]] ; then
+ warn "Could not find a PID in $sidekiq_pidfile"
+ exit 0
+ fi
+
+ if (ps -p $sidekiq_pid -o args | grep '\([0-9]\+\) of \1 busy' 1>&2) ; then
+ warn "Too many busy Sidekiq workers"
+ exit 1
+ fi
+
+ exit 0
+}
+
case "$1" in
stop)
stop
@@ -51,6 +72,9 @@ case "$1" in
killall)
killall
;;
+ load_ok)
+ load_ok
+ ;;
*)
- echo "Usage: RAILS_ENV=your_env $0 {stop|start|start_no_deamonize|restart|killall}"
+ echo "Usage: RAILS_ENV=your_env $0 {stop|start|start_no_deamonize|restart|killall|load_ok}"
esac
diff --git a/script/web b/script/web
index 5464ed040aa..1ad3b5d24b9 100755
--- a/script/web
+++ b/script/web
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
cd $(dirname $0)/..
app_root=$(pwd)
diff --git a/spec/controllers/profile_keys_controller_spec.rb b/spec/controllers/profile_keys_controller_spec.rb
index 121012d5d49..593d3e9eb56 100644
--- a/spec/controllers/profile_keys_controller_spec.rb
+++ b/spec/controllers/profile_keys_controller_spec.rb
@@ -24,6 +24,11 @@ describe Profiles::KeysController do
expect(response.body).to eq("")
end
+
+ it "should respond with text/plain content type" do
+ get :get_keys, username: user.username
+ expect(response.content_type).to eq("text/plain")
+ end
end
describe "user with keys" do
@@ -44,6 +49,11 @@ describe Profiles::KeysController do
expect(response.body).not_to eq("")
expect(response.body).to eq(user.all_ssh_keys.join("\n"))
end
+
+ it "should respond with text/plain content type" do
+ get :get_keys, username: user.username
+ expect(response.content_type).to eq("text/plain")
+ end
end
end
end
diff --git a/spec/factories.rb b/spec/factories.rb
index e5d05a4c2ea..148477d6389 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -32,8 +32,30 @@ FactoryGirl.define do
path { name.downcase.gsub(/\s/, '_') }
namespace
creator
+
+ trait :public do
+ visibility_level Gitlab::VisibilityLevel::PUBLIC
+ end
+
+ trait :internal do
+ visibility_level Gitlab::VisibilityLevel::INTERNAL
+ end
+
+ trait :private do
+ visibility_level Gitlab::VisibilityLevel::PRIVATE
+ end
end
+ # Generates a test repository from the repository stored under `spec/seed_project.tar.gz`.
+ # Once you run `rake gitlab:setup`, you can see what the repository looks like under `tmp/repositories/gitlabhq`.
+ # In order to modify files in the repository, you must untar the seed, modify and remake the tar.
+ # Before recompressing, do not forget to `git checkout master`.
+ # After recompressing, you need to run `RAILS_ENV=test bundle exec rake gitlab:setup` to regenerate the seeds under tmp.
+ #
+ # If you want to modify the repository only for an specific type of tests, e.g., markdown tests,
+ # consider using a feature branch to reduce the chances of collision with other tests.
+ # Create a new commit, and use the same commit message that you will use for the change in the main repo.
+ # Changing the commig message and SHA of branch `master` may break tests.
factory :project, parent: :empty_project do
path { 'gitlabhq' }
@@ -136,6 +158,11 @@ FactoryGirl.define do
state :reopened
end
+ trait :simple do
+ source_branch "simple_merge_request"
+ target_branch "master"
+ end
+
factory :closed_merge_request, traits: [:closed]
factory :reopened_merge_request, traits: [:reopened]
factory :merge_request_with_diffs, traits: [:with_diffs]
@@ -151,7 +178,6 @@ FactoryGirl.define do
factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
factory :note_on_merge_request, traits: [:on_merge_request]
factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff]
- factory :note_on_merge_request_with_attachment, traits: [:on_merge_request, :with_attachment]
trait :on_commit do
project factory: :project
@@ -219,6 +245,19 @@ FactoryGirl.define do
end
end
end
+
+ factory :email do
+ user
+ email do
+ Faker::Internet.email('alias')
+ end
+
+ factory :another_email do
+ email do
+ Faker::Internet.email('another.alias')
+ end
+ end
+ end
factory :milestone do
title
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 1d225e8bad0..b9dab7846b1 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -43,6 +43,31 @@ describe "Issues" do
page.should have_content project.name
end
end
+
+ end
+
+ describe "Editing issue assignee" do
+ let!(:issue) do
+ create(:issue,
+ author: @user,
+ assignee: @user,
+ project: project)
+ end
+
+ it 'allows user to select unasigned', :js => true do
+ visit edit_project_issue_path(project, issue)
+
+ page.should have_content "Assign to #{@user.name}"
+
+ page.first('#s2id_issue_assignee_id').click
+ sleep 2 # wait for ajax stuff to complete
+ page.first('.user-result').click
+
+ click_button "Save changes"
+
+ page.should have_content "Assignee: Select assignee"
+ issue.reload.assignee.should be_nil
+ end
end
describe "Filter issue" do
@@ -183,10 +208,10 @@ describe "Issues" do
it 'with dropdown menu' do
visit project_issue_path(project, issue)
- find('.edit-issue.inline-update').select(project.team.members.first.name, from: 'issue_assignee_id')
+ find('.edit-issue.inline-update #issue_assignee_id').set project.team.members.first.id
click_button 'Update Issue'
- page.should have_content "currently assigned to"
+ page.should have_content "Assignee:"
page.has_select?('issue_assignee_id', :selected => project.team.members.first.name)
end
end
@@ -206,11 +231,9 @@ describe "Issues" do
login_with guest
visit project_issue_path(project, issue)
- page.should have_content "currently assigned to #{issue.assignee.name}"
-
+ page.should have_content issue.assignee.name
end
end
-
end
describe 'update milestone from issue#show' do
@@ -225,17 +248,16 @@ describe "Issues" do
find('.edit-issue.inline-update').select(milestone.title, from: 'issue_milestone_id')
click_button 'Update Issue'
- page.should have_content "Attached to milestone"
+ page.should have_content "Milestone"
page.has_select?('issue_assignee_id', :selected => milestone.title)
end
end
context 'by unauthorized user' do
-
let(:guest) { create(:user) }
before :each do
- project.team << [[guest], :guest]
+ project.team << [guest, :guest]
issue.milestone = milestone
issue.save
end
@@ -245,8 +267,29 @@ describe "Issues" do
login_with guest
visit project_issue_path(project, issue)
+ page.should have_content milestone.title
+ end
+ end
+
+ describe 'removing assignee' do
+ let(:user2) { create(:user) }
+
+ before :each do
+ issue.assignee = user2
+ issue.save
+ end
+
+ it 'allows user to remove assignee', :js => true do
+ visit project_issue_path(project, issue)
+ page.should have_content "Assignee: #{user2.name}"
+
+ page.first('#s2id_issue_assignee_id').click
+ sleep 2 # wait for ajax stuff to complete
+ page.first('.user-result').click
- page.should have_content "Attached to milestone #{milestone.title}"
+ page.should have_content "Assignee: Unassigned"
+ sleep 2 # wait for ajax stuff to complete
+ issue.reload.assignee.should be_nil
end
end
end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index da723ae39bd..25a86b11fa9 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -1,14 +1,12 @@
require 'spec_helper'
describe "On a merge request", js: true do
- let!(:project) { create(:project) }
- let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
- let!(:note) { create(:note_on_merge_request_with_attachment, project: project) }
+ let!(:merge_request) { create(:merge_request, :simple) }
+ let!(:project) { merge_request.source_project }
+ let!(:note) { create(:note_on_merge_request, :with_attachment, project: project) }
before do
- login_as :user
- project.team << [@user, :master]
-
+ login_as :admin
visit project_merge_request_path(project, merge_request)
end
@@ -134,22 +132,20 @@ describe "On a merge request", js: true do
end
end
-describe "On a merge request diff", js: true, focus: true do
- let!(:project) { create(:project) }
- let!(:merge_request) { create(:merge_request_with_diffs, source_project: project, target_project: project) }
+describe "On a merge request diff", js: true do
+ let(:merge_request) { create(:merge_request, :with_diffs, :simple) }
+ let(:project) { merge_request.source_project }
before do
- login_as :user
- project.team << [@user, :master]
+ login_as :admin
visit diffs_project_merge_request_path(project, merge_request)
end
-
subject { page }
describe "when adding a note" do
before do
- find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185"]').click
+ find('a[data-line-code="8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_7_7"]').click
end
describe "the notes holder" do
@@ -160,13 +156,13 @@ describe "On a merge request diff", js: true, focus: true do
describe "the note form" do
it "shouldn't add a second form for same row" do
- find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185"]').click
+ find('a[data-line-code="8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_7_7"]').click
- should have_css("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185'] + .js-temp-notes-holder form", count: 1)
+ should have_css("tr[id='8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_7_7'] + .js-temp-notes-holder form", count: 1)
end
it "should be removed when canceled" do
- within(".file form[rel$='4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185']") do
+ within(".diff-file form[rel$='8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_7_7']") do
find(".js-close-discussion-note-form").trigger("click")
end
@@ -176,12 +172,9 @@ describe "On a merge request diff", js: true, focus: true do
end
describe "with muliple note forms" do
- let!(:project) { create(:project) }
- let!(:merge_request) { create(:merge_request_with_diffs, source_project: project, target_project: project) }
-
before do
- find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185"]').click
- find('a[data-line-code="342e16cbbd482ac2047dc679b2749d248cc1428f_18_17"]').click
+ find('a[data-line-code="8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_7_7"]').click
+ find('a[data-line-code="8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_10_10"]').click
end
it { should have_css(".js-temp-notes-holder", count: 2) }
@@ -189,12 +182,12 @@ describe "On a merge request diff", js: true, focus: true do
describe "previewing them separately" do
before do
# add two separate texts and trigger previews on both
- within("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185'] + .js-temp-notes-holder") do
- fill_in "note[note]", with: "One comment on line 185"
+ within("tr[id='8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_7_7'] + .js-temp-notes-holder") do
+ fill_in "note[note]", with: "One comment on line 7"
find(".js-note-preview-button").trigger("click")
end
- within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do
- fill_in "note[note]", with: "Another comment on line 17"
+ within("tr[id='8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_10_10'] + .js-temp-notes-holder") do
+ fill_in "note[note]", with: "Another comment on line 10"
find(".js-note-preview-button").trigger("click")
end
end
@@ -202,14 +195,14 @@ describe "On a merge request diff", js: true, focus: true do
describe "posting a note" do
before do
- within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do
- fill_in "note[note]", with: "Another comment on line 17"
+ within("tr[id='8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_10_10'] + .js-temp-notes-holder") do
+ fill_in "note[note]", with: "Another comment on line 10"
click_button("Add Comment")
end
end
it 'should be added as discussion' do
- should have_content("Another comment on line 17")
+ should have_content("Another comment on line 10")
should have_css(".notes_holder")
should have_css(".notes_holder .note", count: 1)
should have_link("Reply")
diff --git a/spec/features/security/group_access_spec.rb b/spec/features/security/group/group_access_spec.rb
index dea957962a8..c262d76ab54 100644
--- a/spec/features/security/group_access_spec.rb
+++ b/spec/features/security/group/group_access_spec.rb
@@ -14,6 +14,7 @@ describe "Group access" do
let(:master) { create(:user) }
let(:reporter) { create(:user) }
let(:guest) { create(:user) }
+ let(:nonmember) { create(:user) }
before do
group.add_user(owner, Gitlab::Access::OWNER)
diff --git a/spec/features/security/group/internal_group_access_spec.rb b/spec/features/security/group/internal_group_access_spec.rb
new file mode 100644
index 00000000000..79a6aee41b5
--- /dev/null
+++ b/spec/features/security/group/internal_group_access_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe "Group with internal project access" do
+ describe "Group" do
+ let(:group) { create(:group) }
+
+ let(:owner) { create(:owner) }
+ let(:master) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:nonmember) { create(:user) }
+
+ before do
+ group.add_user(owner, Gitlab::Access::OWNER)
+ group.add_user(master, Gitlab::Access::MASTER)
+ group.add_user(reporter, Gitlab::Access::REPORTER)
+ group.add_user(guest, Gitlab::Access::GUEST)
+
+ create(:project, :internal, group: group)
+ end
+
+ describe "GET /groups/:path" do
+ subject { group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /groups/:path/issues" do
+ subject { issues_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /groups/:path/merge_requests" do
+ subject { merge_requests_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /groups/:path/members" do
+ subject { members_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /groups/:path/edit" do
+ subject { edit_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_denied_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+ end
+end
diff --git a/spec/features/security/group/mixed_group_access_spec.rb b/spec/features/security/group/mixed_group_access_spec.rb
new file mode 100644
index 00000000000..028cd32d2bb
--- /dev/null
+++ b/spec/features/security/group/mixed_group_access_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+describe "Group access" do
+ describe "Group" do
+ let(:group) { create(:group) }
+
+ let(:owner) { create(:owner) }
+ let(:master) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:nonmember) { create(:user) }
+
+ before do
+ group.add_user(owner, Gitlab::Access::OWNER)
+ group.add_user(master, Gitlab::Access::MASTER)
+ group.add_user(reporter, Gitlab::Access::REPORTER)
+ group.add_user(guest, Gitlab::Access::GUEST)
+
+ create(:project, :internal, path: "internal_project", group: group)
+ create(:project, :public, path: "public_project", group: group)
+ end
+
+ describe "GET /groups/:path" do
+ subject { group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /groups/:path/issues" do
+ subject { issues_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /groups/:path/merge_requests" do
+ subject { merge_requests_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /groups/:path/members" do
+ subject { members_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /groups/:path/edit" do
+ subject { edit_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_denied_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+ end
+end
diff --git a/spec/features/security/group/public_group_access_spec.rb b/spec/features/security/group/public_group_access_spec.rb
new file mode 100644
index 00000000000..f0ed7649eb5
--- /dev/null
+++ b/spec/features/security/group/public_group_access_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe "Group with public project access" do
+ describe "Group" do
+ let(:group) { create(:group) }
+
+ let(:owner) { create(:owner) }
+ let(:master) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:nonmember) { create(:user) }
+
+ before do
+ group.add_user(owner, Gitlab::Access::OWNER)
+ group.add_user(master, Gitlab::Access::MASTER)
+ group.add_user(reporter, Gitlab::Access::REPORTER)
+ group.add_user(guest, Gitlab::Access::GUEST)
+
+ create(:project, :public, group: group)
+ end
+
+ describe "GET /groups/:path" do
+ subject { group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /groups/:path/issues" do
+ subject { issues_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /groups/:path/merge_requests" do
+ subject { merge_requests_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /groups/:path/members" do
+ subject { members_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /groups/:path/edit" do
+ subject { edit_group_path(group) }
+
+ it { should be_allowed_for owner }
+ it { should be_denied_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+ end
+end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 8bb1e259efa..152cf66dcfd 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -1,23 +1,18 @@
require 'spec_helper'
describe "Internal Project Access" do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :internal) }
let(:master) { create(:user) }
let(:guest) { create(:user) }
let(:reporter) { create(:user) }
before do
- # internal project
- project.visibility_level = Gitlab::VisibilityLevel::INTERNAL
- project.save!
-
# full access
project.team << [master, :master]
# readonly
project.team << [reporter, :reporter]
-
end
describe "Project should be internal" do
diff --git a/spec/services/filtering_service_spec.rb b/spec/finders/issues_finder_spec.rb
index 92971d0be12..7489e56f423 100644
--- a/spec/services/filtering_service_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -1,13 +1,10 @@
require 'spec_helper'
-describe FilteringService do
+describe IssuesFinder do
let(:user) { create :user }
let(:user2) { create :user }
let(:project1) { create(:project) }
let(:project2) { create(:project) }
- let(:merge_request1) { create(:merge_request, author: user, source_project: project1, target_project: project2) }
- let(:merge_request2) { create(:merge_request, author: user, source_project: project2, target_project: project1) }
- let(:merge_request3) { create(:merge_request, author: user, source_project: project2, target_project: project2) }
let(:issue1) { create(:issue, assignee: user, project: project1) }
let(:issue2) { create(:issue, assignee: user, project: project2) }
let(:issue3) { create(:issue, assignee: user2, project: project2) }
@@ -18,27 +15,7 @@ describe FilteringService do
project2.team << [user2, :developer]
end
- describe 'merge requests' do
- before :each do
- merge_request1
- merge_request2
- merge_request3
- end
-
- it 'should filter by scope' do
- params = { scope: 'authored', state: 'opened' }
- merge_requests = FilteringService.new.execute(MergeRequest, user, params)
- merge_requests.size.should == 3
- end
-
- it 'should filter by project' do
- params = { project_id: project1.id, scope: 'authored', state: 'opened' }
- merge_requests = FilteringService.new.execute(MergeRequest, user, params)
- merge_requests.size.should == 1
- end
- end
-
- describe 'issues' do
+ describe :execute do
before :each do
issue1
issue2
@@ -47,31 +24,31 @@ describe FilteringService do
it 'should filter by all' do
params = { scope: "all", state: 'opened' }
- issues = FilteringService.new.execute(Issue, user, params)
+ issues = IssuesFinder.new.execute(user, params)
issues.size.should == 3
end
it 'should filter by assignee' do
params = { scope: "assigned-to-me", state: 'opened' }
- issues = FilteringService.new.execute(Issue, user, params)
+ issues = IssuesFinder.new.execute(user, params)
issues.size.should == 2
end
it 'should filter by project' do
params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
- issues = FilteringService.new.execute(Issue, user, params)
+ issues = IssuesFinder.new.execute(user, params)
issues.size.should == 1
end
it 'should be empty for unauthorized user' do
params = { scope: "all", state: 'opened' }
- issues = FilteringService.new.execute(Issue, nil, params)
+ issues = IssuesFinder.new.execute(nil, params)
issues.size.should be_zero
end
it 'should not include unauthorized issues' do
params = { scope: "all", state: 'opened' }
- issues = FilteringService.new.execute(Issue, user2, params)
+ issues = IssuesFinder.new.execute(user2, params)
issues.size.should == 2
issues.should_not include(issue1)
issues.should include(issue2)
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
new file mode 100644
index 00000000000..0bd2ccafcc1
--- /dev/null
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe MergeRequestsFinder do
+ let(:user) { create :user }
+ let(:user2) { create :user }
+
+ let(:project1) { create(:project) }
+ let(:project2) { create(:project) }
+
+ let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2) }
+ let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
+ let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2) }
+
+ before do
+ project1.team << [user, :master]
+ project2.team << [user, :developer]
+ project2.team << [user2, :developer]
+ end
+
+ describe "#execute" do
+ it 'should filter by scope' do
+ params = { scope: 'authored', state: 'opened' }
+ merge_requests = MergeRequestsFinder.new.execute(user, params)
+ merge_requests.size.should == 3
+ end
+
+ it 'should filter by project' do
+ params = { project_id: project1.id, scope: 'authored', state: 'opened' }
+ merge_requests = MergeRequestsFinder.new.execute(user, params)
+ merge_requests.size.should == 1
+ end
+ end
+end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
new file mode 100644
index 00000000000..6e3ae4d615b
--- /dev/null
+++ b/spec/finders/projects_finder_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe ProjectsFinder do
+ let(:user) { create :user }
+ let(:group) { create :group }
+
+ let(:project1) { create(:empty_project, :public, group: group) }
+ let(:project2) { create(:empty_project, :internal, group: group) }
+ let(:project3) { create(:empty_project, :private, group: group) }
+ let(:project4) { create(:empty_project, :private, group: group) }
+
+ context 'non authenticated' do
+ subject { ProjectsFinder.new.execute(nil, group: group) }
+
+ it { should include(project1) }
+ it { should_not include(project2) }
+ it { should_not include(project3) }
+ it { should_not include(project4) }
+ end
+
+ context 'authenticated' do
+ subject { ProjectsFinder.new.execute(user, group: group) }
+
+ it { should include(project1) }
+ it { should include(project2) }
+ it { should_not include(project3) }
+ it { should_not include(project4) }
+ end
+
+ context 'authenticated, project member' do
+ before { project3.team << [user, :developer] }
+
+ subject { ProjectsFinder.new.execute(user, group: group) }
+
+ it { should include(project1) }
+ it { should include(project2) }
+ it { should include(project3) }
+ it { should_not include(project4) }
+ end
+
+ context 'authenticated, group member' do
+ before { group.add_user(user, Gitlab::Access::DEVELOPER) }
+
+ subject { ProjectsFinder.new.execute(user, group: group) }
+
+ it { should include(project1) }
+ it { should include(project2) }
+ it { should include(project3) }
+ it { should include(project4) }
+ end
+end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index c58c83a2970..61c561335e5 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -52,7 +52,7 @@ describe ApplicationHelper do
it "should give default avatar_icon when no avatar is present" do
group = create(:group)
group.save!
- group_icon(group.path).to_s.should == "/assets/no_group_avatar.png"
+ group_icon(group.path).should match("group_avatar.png")
end
end
@@ -116,7 +116,45 @@ describe ApplicationHelper do
allow(self).to receive(:request).and_return(double(:ssl? => false))
gravatar_icon(user_email).should == gravatar_icon(user_email.upcase + " ")
end
+ end
+
+ describe "grouped_options_refs" do
+ # Override Rails' grouped_options_for_select helper since HTML is harder to work with
+ def grouped_options_for_select(options, *args)
+ options
+ end
+
+ let(:options) { grouped_options_refs }
+
+ before do
+ # Must be an instance variable
+ @project = create(:project)
+ end
+
+ it "includes a list of branch names" do
+ options[0][0].should == 'Branches'
+ options[0][1].should include('master', 'stable')
+ end
+
+ it "includes a list of tag names" do
+ options[1][0].should == 'Tags'
+ options[1][1].should include('v0.9.4','v1.2.0')
+ end
+
+ it "includes a specific commit ref if defined" do
+ # Must be an instance variable
+ @ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8'
+ options[2][0].should == 'Commit'
+ options[2][1].should == [@ref]
+ end
+
+ it "sorts tags in a natural order" do
+ # Stub repository.tag_names to make sure we get some valid testing data
+ expect(@project.repository).to receive(:tag_names).and_return(["v1.0.9", "v1.0.10", "v2.0", "v3.1.4.2", "v1.0.9a"])
+
+ options[1][1].should == ["v3.1.4.2", "v2.0", "v1.0.10", "v1.0.9a", "v1.0.9"]
+ end
end
describe "user_color_scheme_class" do
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index a445c18f009..5bd16d1c16c 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -440,12 +440,6 @@ describe GitlabMarkdownHelper do
markdown(actual).should match(expected)
end
- it "should handle wiki urls" do
- actual = "[Link](test/link)\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/wikis/test/link\">Link</a></p>\n"
- markdown(actual).should match(expected)
- end
-
it "should handle relative urls in reference links for a file in master" do
actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
expected = "<p><a href=\"/#{project.path_with_namespace}/blob/master/doc/api/README.md\">GitLab API doc</a></p>\n"
@@ -454,7 +448,7 @@ describe GitlabMarkdownHelper do
it "should handle relative urls in reference links for a directory in master" do
actual = "[GitLab API doc directory][GitLab readmes]\n [GitLab readmes]: doc/api/\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/tree/master/doc/api/\">GitLab API doc directory</a></p>\n"
+ expected = "<p><a href=\"/#{project.path_with_namespace}/tree/master/doc/api\">GitLab API doc directory</a></p>\n"
markdown(actual).should match(expected)
end
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index c1efc1fb2a0..dce28525ca4 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -8,7 +8,7 @@ describe NotificationsHelper do
before { notification.stub(disabled?: true) }
it "has a red icon" do
- notification_icon(notification).should match('class="icon-circle cred"')
+ notification_icon(notification).should match('class="icon-volume-off cred"')
end
end
@@ -16,7 +16,7 @@ describe NotificationsHelper do
before { notification.stub(participating?: true) }
it "has a blue icon" do
- notification_icon(notification).should match('class="icon-circle cblue"')
+ notification_icon(notification).should match('class="icon-volume-down cblue"')
end
end
@@ -24,7 +24,7 @@ describe NotificationsHelper do
before { notification.stub(watch?: true) }
it "has a green icon" do
- notification_icon(notification).should match('class="icon-circle cgreen"')
+ notification_icon(notification).should match('class="icon-volume-up cgreen"')
end
end
diff --git a/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb b/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb
index a0e74c49631..501642dca79 100644
--- a/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb
+++ b/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb
@@ -9,7 +9,8 @@ describe Gitlab::LDAP do
@info = double(
uid: '12djsak321',
name: 'John',
- email: 'john@mail.com'
+ email: 'john@mail.com',
+ nickname: 'john'
)
end
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index 4791be41613..76d506eb3c0 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -10,7 +10,7 @@ describe 'Gitlab::Popen', no_db: true do
context 'zero status' do
before do
- @output, @status = @klass.new.popen('ls', path)
+ @output, @status = @klass.new.popen(%W(ls), path)
end
it { @status.should be_zero }
@@ -19,11 +19,27 @@ describe 'Gitlab::Popen', no_db: true do
context 'non-zero status' do
before do
- @output, @status = @klass.new.popen('cat NOTHING', path)
+ @output, @status = @klass.new.popen(%W(cat NOTHING), path)
end
it { @status.should == 1 }
it { @output.should include('No such file or directory') }
end
+
+ context 'unsafe string command' do
+ it 'raises an error when it gets called with a string argument' do
+ expect { @klass.new.popen('ls', path) }.to raise_error
+ end
+ end
+
+ context 'without a directory argument' do
+ before do
+ @output, @status = @klass.new.popen(%W(ls))
+ end
+
+ it { @status.should be_zero }
+ it { @output.should include('spec') }
+ end
+
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index d53dc17d977..22d60429ccd 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -4,6 +4,7 @@ describe Notify do
include EmailSpec::Helpers
include EmailSpec::Matchers
+ let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
let(:recipient) { create(:user, email: 'recipient@example.com') }
let(:project) { create(:project) }
@@ -13,18 +14,28 @@ describe Notify do
end
end
+ shared_examples 'an email sent from GitLab' do
+ it 'is sent from GitLab' do
+ sender = subject.header[:from].addrs[0]
+ sender.display_name.should eq('GitLab')
+ sender.address.should eq(gitlab_sender)
+ end
+ end
+
describe 'for new users, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: 'newguy@example.com', created_by_id: 1) }
subject { Notify.new_user_email(new_user.id, new_user.password) }
+ it_behaves_like 'an email sent from GitLab'
+
it 'is sent to the new user' do
should deliver_to new_user.email
end
it 'has the correct subject' do
- should have_subject /^gitlab \| Account was created for you$/i
+ should have_subject /^Account was created for you$/i
end
it 'contains the new user\'s login name' do
@@ -47,12 +58,14 @@ describe Notify do
subject { Notify.new_user_email(new_user.id, new_user.password) }
+ it_behaves_like 'an email sent from GitLab'
+
it 'is sent to the new user' do
should deliver_to new_user.email
end
it 'has the correct subject' do
- should have_subject /^gitlab \| Account was created for you$/i
+ should have_subject /^Account was created for you$/i
end
it 'contains the new user\'s login name' do
@@ -73,12 +86,14 @@ describe Notify do
subject { Notify.new_ssh_key_email(key.id) }
+ it_behaves_like 'an email sent from GitLab'
+
it 'is sent to the new user' do
should deliver_to key.user.email
end
it 'has the correct subject' do
- should have_subject /^gitlab \| SSH key was added to your account$/i
+ should have_subject /^SSH key was added to your account$/i
end
it 'contains the new ssh key title' do
@@ -90,19 +105,49 @@ describe Notify do
end
end
+ describe 'user added email' do
+ let(:email) { create(:email) }
+
+ subject { Notify.new_email_email(email.id) }
+
+ it 'is sent to the new user' do
+ should deliver_to email.user.email
+ end
+
+ it 'has the correct subject' do
+ should have_subject /^Email was added to your account$/i
+ end
+
+ it 'contains the new email address' do
+ should have_body_text /#{email.email}/
+ end
+
+ it 'includes a link to emails page' do
+ should have_body_text /#{profile_emails_path}/
+ end
+ end
+
context 'for a project' do
describe 'items that are assignable, the email' do
+ let(:current_user) { create(:user, email: "current@email.com") }
let(:assignee) { create(:user, email: 'assignee@example.com') }
let(:previous_assignee) { create(:user, name: 'Previous Assignee') }
shared_examples 'an assignee email' do
+ it 'is sent as the author' do
+ sender = subject.header[:from].addrs[0]
+ sender.display_name.should eq(current_user.name)
+ sender.address.should eq(gitlab_sender)
+ end
+
it 'is sent to the assignee' do
should deliver_to assignee.email
end
end
context 'for issues' do
- let(:issue) { create(:issue, assignee: assignee, project: project ) }
+ let(:issue) { create(:issue, author: current_user, assignee: assignee, project: project) }
+ let(:issue_with_description) { create(:issue, author: current_user, assignee: assignee, project: project, description: Faker::Lorem.sentence) }
describe 'that are new' do
subject { Notify.new_issue_email(issue.assignee_id, issue.id) }
@@ -110,7 +155,7 @@ describe Notify do
it_behaves_like 'an assignee email'
it 'has the correct subject' do
- should have_subject /#{project.name} \| New issue ##{issue.iid} \| #{issue.title}/
+ should have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/
end
it 'contains a link to the new issue' do
@@ -118,15 +163,27 @@ describe Notify do
end
end
- describe 'that have been reassigned' do
- before(:each) { issue.stub(:assignee_id_was).and_return(previous_assignee.id) }
+ describe 'that are new with a description' do
+ subject { Notify.new_issue_email(issue_with_description.assignee_id, issue_with_description.id) }
+
+ it 'contains the description' do
+ should have_body_text /#{issue_with_description.description}/
+ end
+ end
- subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id) }
+ describe 'that have been reassigned' do
+ subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user) }
it_behaves_like 'a multiple recipients email'
+ it 'is sent as the author' do
+ sender = subject.header[:from].addrs[0]
+ sender.display_name.should eq(current_user.name)
+ sender.address.should eq(gitlab_sender)
+ end
+
it 'has the correct subject' do
- should have_subject /Changed issue ##{issue.iid} \| #{issue.title}/
+ should have_subject /#{issue.title} \(##{issue.iid}\)/
end
it 'contains the name of the previous assignee' do
@@ -143,12 +200,17 @@ describe Notify do
end
describe 'status changed' do
- let(:current_user) { create(:user, email: "current@email.com") }
let(:status) { 'closed' }
subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) }
+ it 'is sent as the author' do
+ sender = subject.header[:from].addrs[0]
+ sender.display_name.should eq(current_user.name)
+ sender.address.should eq(gitlab_sender)
+ end
+
it 'has the correct subject' do
- should have_subject /Changed issue ##{issue.iid} \| #{issue.title}/i
+ should have_subject /#{issue.title} \(##{issue.iid}\)/i
end
it 'contains the new status' do
@@ -167,7 +229,9 @@ describe Notify do
end
context 'for merge requests' do
- let(:merge_request) { create(:merge_request, assignee: assignee, source_project: project, target_project: project) }
+ let(:merge_author) { create(:user) }
+ let(:merge_request) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project) }
+ let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: Faker::Lorem.sentence) }
describe 'that are new' do
subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
@@ -175,7 +239,7 @@ describe Notify do
it_behaves_like 'an assignee email'
it 'has the correct subject' do
- should have_subject /New merge request ##{merge_request.iid}/
+ should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/
end
it 'contains a link to the new merge request' do
@@ -191,15 +255,27 @@ describe Notify do
end
end
- describe 'that are reassigned' do
- before(:each) { merge_request.stub(:assignee_id_was).and_return(previous_assignee.id) }
+ describe 'that are new with a description' do
+ subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) }
- subject { Notify.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id) }
+ it 'contains the description' do
+ should have_body_text /#{merge_request_with_description.description}/
+ end
+ end
+
+ describe 'that are reassigned' do
+ subject { Notify.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) }
it_behaves_like 'a multiple recipients email'
+ it 'is sent as the author' do
+ sender = subject.header[:from].addrs[0]
+ sender.display_name.should eq(current_user.name)
+ sender.address.should eq(gitlab_sender)
+ end
+
it 'has the correct subject' do
- should have_subject /Changed merge request ##{merge_request.iid}/
+ should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/
end
it 'contains the name of the previous assignee' do
@@ -213,7 +289,30 @@ describe Notify do
it 'contains a link to the merge request' do
should have_body_text /#{project_merge_request_path project, merge_request}/
end
+ end
+
+ describe 'that are merged' do
+ subject { Notify.merged_merge_request_email(recipient.id, merge_request.id, merge_author.id) }
+
+ it_behaves_like 'a multiple recipients email'
+
+ it 'is sent as the merge author' do
+ sender = subject.header[:from].addrs[0]
+ sender.display_name.should eq(merge_author.name)
+ sender.address.should eq(gitlab_sender)
+ end
+
+ it 'has the correct subject' do
+ should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/
+ end
+
+ it 'contains the new status' do
+ should have_body_text /merged/i
+ end
+ it 'contains a link to the merge request' do
+ should have_body_text /#{project_merge_request_path project, merge_request}/
+ end
end
end
end
@@ -223,6 +322,8 @@ describe Notify do
let(:user) { create(:user) }
subject { Notify.project_was_moved_email(project.id, user.id) }
+ it_behaves_like 'an email sent from GitLab'
+
it 'has the correct subject' do
should have_subject /Project was moved/
end
@@ -243,6 +344,9 @@ describe Notify do
project: project,
user: user) }
subject { Notify.project_access_granted_email(users_project.id) }
+
+ it_behaves_like 'an email sent from GitLab'
+
it 'has the correct subject' do
should have_subject /Access to project was granted/
end
@@ -263,12 +367,14 @@ describe Notify do
end
shared_examples 'a note email' do
- it 'is sent to the given recipient' do
- should deliver_to recipient.email
+ it 'is sent as the author' do
+ sender = subject.header[:from].addrs[0]
+ sender.display_name.should eq(note_author.name)
+ sender.address.should eq(gitlab_sender)
end
- it 'contains the name of the note\'s author' do
- should have_body_text /#{note_author.name}/
+ it 'is sent to the given recipient' do
+ should deliver_to recipient.email
end
it 'contains the message from the note' do
@@ -302,7 +408,7 @@ describe Notify do
it_behaves_like 'a note email'
it 'has the correct subject' do
- should have_subject /Note for commit #{commit.short_id}/
+ should have_subject /#{commit.title} \(#{commit.short_id}\)/
end
it 'contains a link to the commit' do
@@ -320,7 +426,7 @@ describe Notify do
it_behaves_like 'a note email'
it 'has the correct subject' do
- should have_subject /Note for merge request ##{merge_request.iid}/
+ should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/
end
it 'contains a link to the merge request note' do
@@ -338,7 +444,7 @@ describe Notify do
it_behaves_like 'a note email'
it 'has the correct subject' do
- should have_subject /Note for issue ##{issue.iid}/
+ should have_subject /#{issue.title} \(##{issue.iid}\)/
end
it 'contains a link to the issue note' do
@@ -355,6 +461,8 @@ describe Notify do
subject { Notify.group_access_granted_email(membership.id) }
+ it_behaves_like 'an email sent from GitLab'
+
it 'has the correct subject' do
should have_subject /Access to group was granted/
end
@@ -379,6 +487,8 @@ describe Notify do
subject { ActionMailer::Base.deliveries.last }
+ it_behaves_like 'an email sent from GitLab'
+
it 'is sent to the new user' do
should deliver_to 'new-email@mail.com'
end
@@ -396,9 +506,17 @@ describe Notify do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, 'cd5c4bac', 'b1e6a9db') }
+ let(:commits) { Commit.decorate(compare.commits) }
+ let(:diff_path) { project_compare_path(project, from: commits.first, to: commits.last) }
subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) }
+ it 'is sent as the author' do
+ sender = subject.header[:from].addrs[0]
+ sender.display_name.should eq(user.name)
+ sender.address.should eq(gitlab_sender)
+ end
+
it 'is sent to recipient' do
should deliver_to 'devs@company.name'
end
@@ -414,5 +532,9 @@ describe Notify do
it 'includes diffs' do
should have_body_text /Checkout wiki pages for installation information/
end
+
+ it 'contains a link to the diff' do
+ should have_body_text /#{diff_path}/
+ end
end
end
diff --git a/spec/models/assembla_service_spec.rb b/spec/models/assembla_service_spec.rb
index 395aa4a4444..1730a64283a 100644
--- a/spec/models/assembla_service_spec.rb
+++ b/spec/models/assembla_service_spec.rb
@@ -13,6 +13,7 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
require 'spec_helper'
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 0827e4f162b..9cbc8990676 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -25,11 +25,6 @@ describe Issue, "Issuable" do
it { described_class.should respond_to(:assigned) }
end
- it "has an :author_id_of_changes accessor" do
- issue.should respond_to(:author_id_of_changes)
- issue.should respond_to(:author_id_of_changes=)
- end
-
describe ".search" do
let!(:searchable_issue) { create(:issue, title: "Searchable issue") }
diff --git a/spec/models/flowdock_service_spec.rb b/spec/models/flowdock_service_spec.rb
index cd553b33ad7..97414585331 100644
--- a/spec/models/flowdock_service_spec.rb
+++ b/spec/models/flowdock_service_spec.rb
@@ -13,6 +13,7 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
require 'spec_helper'
diff --git a/spec/models/gemnasium_service_spec.rb b/spec/models/gemnasium_service_spec.rb
new file mode 100644
index 00000000000..dfc99849d60
--- /dev/null
+++ b/spec/models/gemnasium_service_spec.rb
@@ -0,0 +1,47 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# token :string(255)
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean default(FALSE), not null
+# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
+# api_key :string(255)
+#
+
+require 'spec_helper'
+
+describe GemnasiumService do
+ describe "Associations" do
+ it { should belong_to :project }
+ it { should have_one :service_hook }
+ end
+
+ describe "Execute" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ @gemnasium_service = GemnasiumService.new
+ @gemnasium_service.stub(
+ project_id: project.id,
+ project: project,
+ service_hook: true,
+ token: 'verySecret',
+ api_key: 'GemnasiumUserApiKey'
+ )
+ @sample_data = GitPushService.new.sample_data(project, user)
+ end
+ it "should call Gemnasium service" do
+ Gemnasium::GitlabService.should_receive(:execute).with(an_instance_of(Hash)).once
+ @gemnasium_service.execute(@sample_data)
+ end
+ end
+end
diff --git a/spec/models/gitlab_ci_service_spec.rb b/spec/models/gitlab_ci_service_spec.rb
index 56efa9df457..8ec15cb3466 100644
--- a/spec/models/gitlab_ci_service_spec.rb
+++ b/spec/models/gitlab_ci_service_spec.rb
@@ -13,6 +13,7 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
require 'spec_helper'
diff --git a/spec/models/gollum_wiki_spec.rb b/spec/models/gollum_wiki_spec.rb
index 9e07d9ee191..9b9d15b4ff4 100644
--- a/spec/models/gollum_wiki_spec.rb
+++ b/spec/models/gollum_wiki_spec.rb
@@ -2,12 +2,6 @@ require "spec_helper"
describe GollumWiki do
- def create_temp_repo(path)
- FileUtils.mkdir_p path
- command = "git init --quiet #{path};"
- system(command)
- end
-
def remove_temp_repo(path)
FileUtils.rm_rf path
end
diff --git a/spec/models/project_hook_spec.rb b/spec/models/project_hook_spec.rb
new file mode 100644
index 00000000000..7bd7c431bcd
--- /dev/null
+++ b/spec/models/project_hook_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe ProjectHook do
+ describe '.push_hooks' do
+ it 'should return hooks for push events only' do
+ hook = create(:project_hook, push_events: true)
+ hook2 = create(:project_hook, push_events: false)
+ expect(ProjectHook.push_hooks).to eq([hook])
+ end
+ end
+
+ describe '.tag_push_hooks' do
+ it 'should return hooks for tag push events only' do
+ hook = create(:project_hook, tag_push_events: true)
+ hook2 = create(:project_hook, tag_push_events: false)
+ expect(ProjectHook.tag_push_hooks).to eq([hook])
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 6bae5951b7b..839350bafbf 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -47,6 +47,7 @@ describe Project do
it { should have_many(:hooks).dependent(:destroy) }
it { should have_many(:protected_branches).dependent(:destroy) }
it { should have_one(:forked_project_link).dependent(:destroy) }
+ it { should have_one(:slack_service).dependent(:destroy) }
end
describe "Mass assignment" do
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 46b3bf39aeb..94542074967 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -13,6 +13,7 @@
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# api_key :string(255)
#
require 'spec_helper'
diff --git a/spec/models/slack_message_spec.rb b/spec/models/slack_message_spec.rb
new file mode 100644
index 00000000000..b39cd4edf82
--- /dev/null
+++ b/spec/models/slack_message_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../app/models/project_services/slack_message'
+
+describe SlackMessage do
+ subject { SlackMessage.new(args) }
+
+ let(:args) {
+ {
+ after: 'after',
+ before: 'before',
+ project_name: 'project_name',
+ ref: 'refs/heads/master',
+ user_name: 'user_name',
+ project_url: 'url'
+ }
+ }
+
+ context 'push' do
+ before do
+ args[:commits] = [
+ { message: 'message1', url: 'url1', id: 'abcdefghi' },
+ { message: 'message2', url: 'url2', id: '123456789' },
+ ]
+ end
+
+ it 'returns a message regarding pushes' do
+ subject.compose.should ==
+ 'user_name pushed to branch <url/commits/master|master> of ' <<
+ '<url|project_name> (<url/compare/before...after|Compare changes>)' <<
+ "\n - message1 (<url1|abcdef>)" <<
+ "\n - message2 (<url2|123456>)"
+ end
+ end
+
+ context 'new branch' do
+ before do
+ args[:before] = '000000'
+ end
+
+ it 'returns a message regarding a new branch' do
+ subject.compose.should ==
+ 'user_name pushed new branch <url/commits/master|master> to ' <<
+ '<url|project_name>'
+ end
+ end
+
+ context 'removed branch' do
+ before do
+ args[:after] = '000000'
+ end
+
+ it 'returns a message regarding a removed branch' do
+ subject.compose.should ==
+ 'user_name removed branch master from <url|project_name>'
+ end
+ end
+end
diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb
new file mode 100644
index 00000000000..387455cb25e
--- /dev/null
+++ b/spec/models/slack_service_spec.rb
@@ -0,0 +1,69 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# token :string(255)
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean default(FALSE), not null
+# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
+# api_key :string(255)
+#
+
+require 'spec_helper'
+
+describe SlackService do
+ describe "Associations" do
+ it { should belong_to :project }
+ it { should have_one :service_hook }
+ end
+
+ describe "Validations" do
+ context "active" do
+ before do
+ subject.active = true
+ end
+
+ it { should validate_presence_of :room }
+ it { should validate_presence_of :subdomain }
+ it { should validate_presence_of :token }
+ end
+ end
+
+ describe "Execute" do
+ let(:slack) { SlackService.new }
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:sample_data) { GitPushService.new.sample_data(project, user) }
+ let(:subdomain) { 'gitlab' }
+ let(:token) { 'verySecret' }
+ let(:api_url) {
+ "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{token}"
+ }
+
+ before do
+ slack.stub(
+ project: project,
+ project_id: project.id,
+ room: '#gitlab',
+ service_hook: true,
+ subdomain: subdomain,
+ token: token
+ )
+
+ WebMock.stub_request(:post, api_url)
+ end
+
+ it "should call Slack API" do
+ slack.execute(sample_data)
+
+ WebMock.should have_requested(:post, api_url).once
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index fd8d7133ae9..fef6314f23a 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -292,6 +292,20 @@ describe User do
end
end
+ describe 'search' do
+ let(:user1) { create(:user, username: 'James', email: 'james@testing.com') }
+ let(:user2) { create(:user, username: 'jameson', email: 'jameson@example.com') }
+
+ it "should be case insensitive" do
+ User.search(user1.username.upcase).to_a.should == [user1]
+ User.search(user1.username.downcase).to_a.should == [user1]
+ User.search(user2.username.upcase).to_a.should == [user2]
+ User.search(user2.username.downcase).to_a.should == [user2]
+ User.search(user1.username.downcase).to_a.count.should == 2
+ User.search(user2.username.downcase).to_a.count.should == 1
+ end
+ end
+
describe 'by_username_or_id' do
let(:user1) { create(:user, username: 'foo') }
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 67f2a6da42d..1c70edf0d4d 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -2,12 +2,6 @@ require "spec_helper"
describe WikiPage do
- def create_temp_repo(path)
- FileUtils.mkdir_p path
- command = "git init --quiet #{path};"
- system(command)
- end
-
def remove_temp_repo(path)
FileUtils.rm_rf path
end
diff --git a/spec/observers/activity_observer_spec.rb b/spec/observers/activity_observer_spec.rb
deleted file mode 100644
index dc14ab86b6d..00000000000
--- a/spec/observers/activity_observer_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'spec_helper'
-
-describe ActivityObserver do
- let(:project) { create(:project) }
-
- before { Thread.current[:current_user] = create(:user) }
-
- def self.it_should_be_valid_event
- it { @event.should_not be_nil }
- it { @event.project.should == project }
- end
-
- describe "Issue created" do
- before do
- Issue.observers.enable :activity_observer do
- @issue = create(:issue, project: project)
- @event = Event.last
- end
- end
-
- it_should_be_valid_event
- it { @event.action.should == Event::CREATED }
- it { @event.target.should == @issue }
- end
-
- describe "Issue commented" do
- before do
- Note.observers.enable :activity_observer do
- @issue = create(:issue, project: project)
- @note = create(:note, noteable: @issue, project: project, author: @issue.author)
- @event = Event.last
- end
- end
-
- it_should_be_valid_event
- it { @event.action.should == Event::COMMENTED }
- it { @event.target.should == @note }
- end
-
- describe "Ignore system notes" do
- let(:author) { create(:user) }
- let!(:issue) { create(:issue, project: project) }
- let!(:other) { create(:issue) }
-
- it "should not create events for status change notes" do
- expect do
- Note.observers.enable :activity_observer do
- Note.create_status_change_note(issue, project, author, 'reopened', nil)
- end
- end.to_not change { Event.count }
- end
-
- it "should not create events for cross-reference notes" do
- expect do
- Note.observers.enable :activity_observer do
- Note.create_cross_reference_note(issue, other, author, issue.project)
- end
- end.to_not change { Event.count }
- end
- end
-end
diff --git a/spec/observers/email_observer_spec.rb b/spec/observers/email_observer_spec.rb
new file mode 100644
index 00000000000..599b9a6ffba
--- /dev/null
+++ b/spec/observers/email_observer_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe EmailObserver do
+ let(:email) { create(:email) }
+
+ before { subject.stub(notification: double('NotificationService').as_null_object) }
+
+ subject { EmailObserver.instance }
+
+ describe '#after_create' do
+ it 'trigger notification to send emails' do
+ subject.should_receive(:notification)
+
+ subject.after_create(email)
+ end
+ end
+end
diff --git a/spec/observers/merge_request_observer_spec.rb b/spec/observers/merge_request_observer_spec.rb
index 6ad7c4d81da..18df8b78513 100644
--- a/spec/observers/merge_request_observer_spec.rb
+++ b/spec/observers/merge_request_observer_spec.rb
@@ -120,7 +120,7 @@ describe MergeRequestObserver do
end
before do
- @merge_request = create(:merge_request, source_project: project, source_project: project)
+ @merge_request = create(:merge_request, source_project: project, target_project: project)
@event = Event.last
end
diff --git a/spec/observers/users_project_observer_spec.rb b/spec/observers/users_project_observer_spec.rb
index dea90b2bfa7..be277b4dbd2 100644
--- a/spec/observers/users_project_observer_spec.rb
+++ b/spec/observers/users_project_observer_spec.rb
@@ -47,30 +47,6 @@ describe UsersProjectObserver do
end
describe "#after_create" do
- context 'wiki_enabled creates repository directory' do
- context 'wiki_enabled true creates wiki repository directory' do
- before do
- @project = create(:project, wiki_enabled: true)
- @path = GollumWiki.new(@project, user).send(:path_to_repo)
- end
-
- after do
- FileUtils.rm_rf(@path)
- end
-
- it { File.exists?(@path).should be_true }
- end
-
- context 'wiki_enabled false does not create wiki repository directory' do
- before do
- @project = create(:project, wiki_enabled: false)
- @path = GollumWiki.new(@project, user).send(:path_to_repo)
- end
-
- it { File.exists?(@path).should be_false }
- end
- end
-
it "should send email to user" do
subject.should_receive(:notification)
Event.stub(create: true)
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
new file mode 100644
index 00000000000..ea317e1137a
--- /dev/null
+++ b/spec/requests/api/commits_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+require 'mime/types'
+
+describe API::API do
+ include ApiHelpers
+ before(:each) { enable_observers }
+ after(:each) {disable_observers}
+
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:project) { create(:project, creator_id: user.id) }
+ let!(:master) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
+ let!(:guest) { create(:users_project, user: user2, project: project, project_access: UsersProject::GUEST) }
+
+ before { project.team << [user, :reporter] }
+
+ describe "GET /projects/:id/repository/commits" do
+ context "authorized user" do
+ before { project.team << [user2, :reporter] }
+
+ it "should return project commits" do
+ get api("/projects/#{project.id}/repository/commits", user)
+ response.status.should == 200
+
+ json_response.should be_an Array
+ json_response.first['id'].should == project.repository.commit.id
+ end
+ end
+
+ context "unauthorized user" do
+ it "should not return project commits" do
+ get api("/projects/#{project.id}/repository/commits")
+ response.status.should == 401
+ end
+ end
+ end
+
+ describe "GET /projects:id/repository/commits/:sha" do
+ context "authorized user" do
+ it "should return a commit by sha" do
+ get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+ response.status.should == 200
+ json_response['id'].should == project.repository.commit.id
+ json_response['title'].should == project.repository.commit.title
+ end
+
+ it "should return a 404 error if not found" do
+ get api("/projects/#{project.id}/repository/commits/invalid_sha", user)
+ response.status.should == 404
+ end
+ end
+
+ context "unauthorized user" do
+ it "should not return the selected commit" do
+ get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}")
+ response.status.should == 401
+ end
+ end
+ end
+
+ describe "GET /projects:id/repository/commits/:sha/diff" do
+ context "authorized user" do
+ before { project.team << [user2, :reporter] }
+
+ it "should return the diff of the selected commit" do
+ get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user)
+ response.status.should == 200
+
+ json_response.should be_an Array
+ json_response.length.should >= 1
+ json_response.first.keys.should include "diff"
+ end
+
+ it "should return a 404 error if invalid commit" do
+ get api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user)
+ response.status.should == 404
+ end
+ end
+
+ context "unauthorized user" do
+ it "should not return the diff of the selected commit" do
+ get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff")
+ response.status.should == 401
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index acef7df8777..fa25a4bec6a 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -9,6 +9,36 @@ describe API::API do
let!(:project) { create(:project, namespace: user.namespace ) }
before { project.team << [user, :developer] }
+ describe "GET /projects/:id/repository/files" do
+ it "should return file info" do
+ params = {
+ file_path: 'app/models/key.rb',
+ ref: 'master',
+ }
+
+ get api("/projects/#{project.id}/repository/files", user), params
+ response.status.should == 200
+ json_response['file_path'].should == 'app/models/key.rb'
+ json_response['file_name'].should == 'key.rb'
+ Base64.decode64(json_response['content']).lines.first.should == "class Key < ActiveRecord::Base\n"
+ end
+
+ it "should return a 400 bad request if no params given" do
+ get api("/projects/#{project.id}/repository/files", user)
+ response.status.should == 400
+ end
+
+ it "should return a 404 if such file does not exist" do
+ params = {
+ file_path: 'app/models/application.rb',
+ ref: 'master',
+ }
+
+ get api("/projects/#{project.id}/repository/files", user), params
+ response.status.should == 404
+ end
+ end
+
describe "POST /projects/:id/repository/files" do
let(:valid_params) {
{
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index c55025d72b5..e9422cd2643 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -100,16 +100,4 @@ describe API::API do
response.status.should == 405
end
end
-
- describe "PUT /projects/:id/issues/:issue_id to test observer on close" do
- before { enable_observers }
- after { disable_observers }
-
- it "should create an activity event when an issue is closed" do
- Event.should_receive(:create)
-
- put api("/projects/#{project.id}/issues/#{issue.id}", user),
- state_event: "close"
- end
- end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 412b6c95ffa..138f218d46c 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -7,6 +7,7 @@ describe API::API do
let(:user) { create(:user) }
let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:merge_request) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
+ let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
before {
project.team << [user, :reporters]
}
@@ -92,9 +93,10 @@ describe API::API do
it "should return merge_request" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
- title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id
+ title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
response.status.should == 201
json_response['title'].should == 'Test merge_request'
+ json_response['description'].should == 'Test description for Test merge_request'
end
it "should not return 422 when source_branch equals target_branch" do
@@ -168,6 +170,12 @@ describe API::API do
json_response['title'].should == 'New title'
end
+ it "should return merge_request" do
+ put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), description: "New description"
+ response.status.should == 200
+ json_response['description'].should == 'New description'
+ end
+
it "should return 422 when source_branch and target_branch are renamed the same" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
@@ -198,4 +206,20 @@ describe API::API do
response.status.should == 404
end
end
+
+ describe "GET :id/merge_request/:merge_request_id/comments" do
+ it "should return merge_request comments" do
+ get api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.length.should == 1
+ json_response.first['note'].should == "a comment on a MR"
+ json_response.first['author']['id'].should == user.id
+ end
+
+ it "should return a 404 error if merge_request_id not found" do
+ get api("/projects/#{project.id}/merge_request/999/comments", user)
+ response.status.should == 404
+ end
+ end
end
diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb
new file mode 100644
index 00000000000..f3c0529da92
--- /dev/null
+++ b/spec/requests/api/project_members_spec.rb
@@ -0,0 +1,156 @@
+require 'spec_helper'
+
+describe API::API do
+ include ApiHelpers
+ before(:each) { enable_observers }
+ after(:each) { disable_observers }
+
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:user3) { create(:user) }
+ let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
+ let(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
+ let(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
+
+ describe "GET /projects/:id/members" do
+ before { users_project }
+ before { users_project2 }
+
+ it "should return project team members" do
+ get api("/projects/#{project.id}/members", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.count.should == 2
+ json_response.map { |u| u['email'] }.should include user.email
+ end
+
+ it "finds team members with query string" do
+ get api("/projects/#{project.id}/members", user), query: user.username
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.count.should == 1
+ json_response.first['email'].should == user.email
+ end
+
+ it "should return a 404 error if id not found" do
+ get api("/projects/9999/members", user)
+ response.status.should == 404
+ end
+ end
+
+ describe "GET /projects/:id/members/:user_id" do
+ before { users_project }
+
+ it "should return project team member" do
+ get api("/projects/#{project.id}/members/#{user.id}", user)
+ response.status.should == 200
+ json_response['email'].should == user.email
+ json_response['access_level'].should == UsersProject::MASTER
+ end
+
+ it "should return a 404 error if user id not found" do
+ get api("/projects/#{project.id}/members/1234", user)
+ response.status.should == 404
+ end
+ end
+
+ describe "POST /projects/:id/members" do
+ it "should add user to project team" do
+ expect {
+ post api("/projects/#{project.id}/members", user), user_id: user2.id,
+ access_level: UsersProject::DEVELOPER
+ }.to change { UsersProject.count }.by(1)
+
+ response.status.should == 201
+ json_response['email'].should == user2.email
+ json_response['access_level'].should == UsersProject::DEVELOPER
+ end
+
+ it "should return a 201 status if user is already project member" do
+ post api("/projects/#{project.id}/members", user), user_id: user2.id,
+ access_level: UsersProject::DEVELOPER
+ expect {
+ post api("/projects/#{project.id}/members", user), user_id: user2.id,
+ access_level: UsersProject::DEVELOPER
+ }.not_to change { UsersProject.count }.by(1)
+
+ response.status.should == 201
+ json_response['email'].should == user2.email
+ json_response['access_level'].should == UsersProject::DEVELOPER
+ end
+
+ it "should return a 400 error when user id is not given" do
+ post api("/projects/#{project.id}/members", user), access_level: UsersProject::MASTER
+ response.status.should == 400
+ end
+
+ it "should return a 400 error when access level is not given" do
+ post api("/projects/#{project.id}/members", user), user_id: user2.id
+ response.status.should == 400
+ end
+
+ it "should return a 422 error when access level is not known" do
+ post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: 1234
+ response.status.should == 422
+ end
+ end
+
+ describe "PUT /projects/:id/members/:user_id" do
+ before { users_project2 }
+
+ it "should update project team member" do
+ put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: UsersProject::MASTER
+ response.status.should == 200
+ json_response['email'].should == user3.email
+ json_response['access_level'].should == UsersProject::MASTER
+ end
+
+ it "should return a 404 error if user_id is not found" do
+ put api("/projects/#{project.id}/members/1234", user), access_level: UsersProject::MASTER
+ response.status.should == 404
+ end
+
+ it "should return a 400 error when access level is not given" do
+ put api("/projects/#{project.id}/members/#{user3.id}", user)
+ response.status.should == 400
+ end
+
+ it "should return a 422 error when access level is not known" do
+ put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: 123
+ response.status.should == 422
+ end
+ end
+
+ describe "DELETE /projects/:id/members/:user_id" do
+ before { users_project }
+ before { users_project2 }
+
+ it "should remove user from project team" do
+ expect {
+ delete api("/projects/#{project.id}/members/#{user3.id}", user)
+ }.to change { UsersProject.count }.by(-1)
+ end
+
+ it "should return 200 if team member is not part of a project" do
+ delete api("/projects/#{project.id}/members/#{user3.id}", user)
+ expect {
+ delete api("/projects/#{project.id}/members/#{user3.id}", user)
+ }.to_not change { UsersProject.count }.by(1)
+ end
+
+ it "should return 200 if team member already removed" do
+ delete api("/projects/#{project.id}/members/#{user3.id}", user)
+ delete api("/projects/#{project.id}/members/#{user3.id}", user)
+ response.status.should == 200
+ end
+
+ it "should return 200 OK when the user was not member" do
+ expect {
+ delete api("/projects/#{project.id}/members/1000000", user)
+ }.to change { UsersProject.count }.by(0)
+ response.status.should == 200
+ json_response['message'].should == "Access revoked"
+ json_response['id'].should == 1000000
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 342587ba5d6..7fe65639657 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -13,6 +13,7 @@ describe API::API do
let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
let(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
let(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
+ let(:issue_with_labels) { create(:issue, author: user, assignee: user, project: project, :label_list => "label1, label2") }
describe "GET /projects" do
before { project }
@@ -133,7 +134,7 @@ describe API::API do
end
it "should set a project as public" do
- project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PUBLIC })
+ project = attributes_for(:project, :public)
post api("/projects", user), project
json_response['public'].should be_true
json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
@@ -147,21 +148,21 @@ describe API::API do
end
it "should set a project as internal" do
- project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::INTERNAL })
+ project = attributes_for(:project, :internal)
post api("/projects", user), project
json_response['public'].should be_false
json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
end
it "should set a project as internal overriding :public" do
- project = attributes_for(:project, { public: true, visibility_level: Gitlab::VisibilityLevel::INTERNAL })
+ project = attributes_for(:project, :internal, { public: true })
post api("/projects", user), project
json_response['public'].should be_false
json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
end
it "should set a project as private" do
- project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
+ project = attributes_for(:project, :private)
post api("/projects", user), project
json_response['public'].should be_false
json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
@@ -215,7 +216,7 @@ describe API::API do
end
it "should set a project as public" do
- project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PUBLIC })
+ project = attributes_for(:project, :public)
post api("/projects/user/#{user.id}", admin), project
json_response['public'].should be_true
json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
@@ -229,21 +230,21 @@ describe API::API do
end
it "should set a project as internal" do
- project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::INTERNAL })
+ project = attributes_for(:project, :internal)
post api("/projects/user/#{user.id}", admin), project
json_response['public'].should be_false
json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
end
it "should set a project as internal overriding :public" do
- project = attributes_for(:project, { public: true, visibility_level: Gitlab::VisibilityLevel::INTERNAL })
+ project = attributes_for(:project, :internal, { public: true })
post api("/projects/user/#{user.id}", admin), project
json_response['public'].should be_false
json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
end
it "should set a project as private" do
- project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
+ project = attributes_for(:project, :private)
post api("/projects/user/#{user.id}", admin), project
json_response['public'].should be_false
json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
@@ -259,6 +260,7 @@ describe API::API do
describe "GET /projects/:id" do
before { project }
+ before { users_project }
it "should return a project by id" do
get api("/projects/#{project.id}", user)
@@ -284,6 +286,28 @@ describe API::API do
get api("/projects/#{project.id}", other_user)
response.status.should == 404
end
+
+ describe 'permissions' do
+ context 'personal project' do
+ before { get api("/projects/#{project.id}", user) }
+
+ it { response.status.should == 200 }
+ it { json_response['permissions']["project_access"]["access_level"].should == Gitlab::Access::MASTER }
+ it { json_response['permissions']["group_access"].should be_nil }
+ end
+
+ context 'group project' do
+ before do
+ project2 = create(:project, group: create(:group))
+ project2.group.add_owner(user)
+ get api("/projects/#{project2.id}", user)
+ end
+
+ it { response.status.should == 200 }
+ it { json_response['permissions']["project_access"].should be_nil }
+ it { json_response['permissions']["group_access"]["access_level"].should == Gitlab::Access::OWNER }
+ end
+ end
end
describe "GET /projects/:id/events" do
@@ -311,148 +335,6 @@ describe API::API do
end
end
- describe "GET /projects/:id/members" do
- before { users_project }
- before { users_project2 }
-
- it "should return project team members" do
- get api("/projects/#{project.id}/members", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.count.should == 2
- json_response.map { |u| u['email'] }.should include user.email
- end
-
- it "finds team members with query string" do
- get api("/projects/#{project.id}/members", user), query: user.username
- response.status.should == 200
- json_response.should be_an Array
- json_response.count.should == 1
- json_response.first['email'].should == user.email
- end
-
- it "should return a 404 error if id not found" do
- get api("/projects/9999/members", user)
- response.status.should == 404
- end
- end
-
- describe "GET /projects/:id/members/:user_id" do
- before { users_project }
-
- it "should return project team member" do
- get api("/projects/#{project.id}/members/#{user.id}", user)
- response.status.should == 200
- json_response['email'].should == user.email
- json_response['access_level'].should == UsersProject::MASTER
- end
-
- it "should return a 404 error if user id not found" do
- get api("/projects/#{project.id}/members/1234", user)
- response.status.should == 404
- end
- end
-
- describe "POST /projects/:id/members" do
- it "should add user to project team" do
- expect {
- post api("/projects/#{project.id}/members", user), user_id: user2.id,
- access_level: UsersProject::DEVELOPER
- }.to change { UsersProject.count }.by(1)
-
- response.status.should == 201
- json_response['email'].should == user2.email
- json_response['access_level'].should == UsersProject::DEVELOPER
- end
-
- it "should return a 201 status if user is already project member" do
- post api("/projects/#{project.id}/members", user), user_id: user2.id,
- access_level: UsersProject::DEVELOPER
- expect {
- post api("/projects/#{project.id}/members", user), user_id: user2.id,
- access_level: UsersProject::DEVELOPER
- }.not_to change { UsersProject.count }.by(1)
-
- response.status.should == 201
- json_response['email'].should == user2.email
- json_response['access_level'].should == UsersProject::DEVELOPER
- end
-
- it "should return a 400 error when user id is not given" do
- post api("/projects/#{project.id}/members", user), access_level: UsersProject::MASTER
- response.status.should == 400
- end
-
- it "should return a 400 error when access level is not given" do
- post api("/projects/#{project.id}/members", user), user_id: user2.id
- response.status.should == 400
- end
-
- it "should return a 422 error when access level is not known" do
- post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: 1234
- response.status.should == 422
- end
- end
-
- describe "PUT /projects/:id/members/:user_id" do
- before { users_project2 }
-
- it "should update project team member" do
- put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: UsersProject::MASTER
- response.status.should == 200
- json_response['email'].should == user3.email
- json_response['access_level'].should == UsersProject::MASTER
- end
-
- it "should return a 404 error if user_id is not found" do
- put api("/projects/#{project.id}/members/1234", user), access_level: UsersProject::MASTER
- response.status.should == 404
- end
-
- it "should return a 400 error when access level is not given" do
- put api("/projects/#{project.id}/members/#{user3.id}", user)
- response.status.should == 400
- end
-
- it "should return a 422 error when access level is not known" do
- put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: 123
- response.status.should == 422
- end
- end
-
- describe "DELETE /projects/:id/members/:user_id" do
- before { users_project }
- before { users_project2 }
-
- it "should remove user from project team" do
- expect {
- delete api("/projects/#{project.id}/members/#{user3.id}", user)
- }.to change { UsersProject.count }.by(-1)
- end
-
- it "should return 200 if team member is not part of a project" do
- delete api("/projects/#{project.id}/members/#{user3.id}", user)
- expect {
- delete api("/projects/#{project.id}/members/#{user3.id}", user)
- }.to_not change { UsersProject.count }.by(1)
- end
-
- it "should return 200 if team member already removed" do
- delete api("/projects/#{project.id}/members/#{user3.id}", user)
- delete api("/projects/#{project.id}/members/#{user3.id}", user)
- response.status.should == 200
- end
-
- it "should return 200 OK when the user was not member" do
- expect {
- delete api("/projects/#{project.id}/members/1000000", user)
- }.to change { UsersProject.count }.by(0)
- response.status.should == 200
- json_response['message'].should == "Access revoked"
- json_response['id'].should == 1000000
- end
- end
-
describe "GET /projects/:id/snippets" do
before { snippet }
@@ -609,10 +491,10 @@ describe API::API do
describe :fork_admin do
let(:project_fork_target) { create(:project) }
- let(:project_fork_source) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+ let(:project_fork_source) { create(:project, :public) }
describe "POST /projects/:id/fork/:forked_from_id" do
- let(:new_project_fork_source) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+ let(:new_project_fork_source) { create(:project, :public) }
it "shouldn't available for non admin users" do
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
@@ -681,10 +563,10 @@ describe API::API do
let!(:post) { create(:empty_project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) }
let!(:pre_post) { create(:empty_project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) }
let!(:unfound) { create(:empty_project, name: 'unfound', creator_id: user.id, namespace: user.namespace) }
- let!(:internal) { create(:empty_project, name: "internal #{query}", visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
- let!(:unfound_internal) { create(:empty_project, name: 'unfound internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
- let!(:public) { create(:empty_project, name: "public #{query}", visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let!(:unfound_public) { create(:empty_project, name: 'unfound public', visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+ let!(:internal) { create(:empty_project, :internal, name: "internal #{query}") }
+ let!(:unfound_internal) { create(:empty_project, :internal, name: 'unfound internal') }
+ let!(:public) { create(:empty_project, :public, name: "public #{query}") }
+ let!(:unfound_public) { create(:empty_project, :public, name: 'unfound public') }
context "when unauthenticated" do
it "should return authentication error" do
@@ -751,4 +633,16 @@ describe API::API do
end
end
end
+
+ describe "GET /projects/:id/labels" do
+ before { issue_with_labels }
+
+ it "should return project labels" do
+ get api("/projects/#{project.id}/labels", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['name'].should == issue_with_labels.labels.first.name
+ json_response.last['name'].should == issue_with_labels.labels.last.name
+ end
+ end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 47008728252..99d966edc38 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -103,77 +103,6 @@ describe API::API do
end
end
- describe "GET /projects/:id/repository/commits" do
- context "authorized user" do
- before { project.team << [user2, :reporter] }
-
- it "should return project commits" do
- get api("/projects/#{project.id}/repository/commits", user)
- response.status.should == 200
-
- json_response.should be_an Array
- json_response.first['id'].should == project.repository.commit.id
- end
- end
-
- context "unauthorized user" do
- it "should not return project commits" do
- get api("/projects/#{project.id}/repository/commits")
- response.status.should == 401
- end
- end
- end
-
- describe "GET /projects:id/repository/commits/:sha" do
- context "authorized user" do
- it "should return a commit by sha" do
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
- response.status.should == 200
- json_response['id'].should == project.repository.commit.id
- json_response['title'].should == project.repository.commit.title
- end
-
- it "should return a 404 error if not found" do
- get api("/projects/#{project.id}/repository/commits/invalid_sha", user)
- response.status.should == 404
- end
- end
-
- context "unauthorized user" do
- it "should not return the selected commit" do
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}")
- response.status.should == 401
- end
- end
- end
-
- describe "GET /projects:id/repository/commits/:sha/diff" do
- context "authorized user" do
- before { project.team << [user2, :reporter] }
-
- it "should return the diff of the selected commit" do
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user)
- response.status.should == 200
-
- json_response.should be_an Array
- json_response.length.should >= 1
- json_response.first.keys.should include "diff"
- end
-
- it "should return a 404 error if invalid commit" do
- get api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user)
- response.status.should == 404
- end
- end
-
- context "unauthorized user" do
- it "should not return the diff of the selected commit" do
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff")
- response.status.should == 401
- end
- end
- end
-
describe "GET /projects/:id/repository/tree" do
context "authorized user" do
before { project.team << [user2, :reporter] }
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 8929a48973d..9b67cd432bc 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -183,6 +183,23 @@ describe Profiles::KeysController, "routing" do
end
end
+# emails GET /emails(.:format) emails#index
+# POST /keys(.:format) emails#create
+# DELETE /keys/:id(.:format) keys#destroy
+describe Profiles::EmailsController, "routing" do
+ it "to #index" do
+ get("/profile/emails").should route_to('profiles/emails#index')
+ end
+
+ it "to #create" do
+ post("/profile/emails").should route_to('profiles/emails#create')
+ end
+
+ it "to #destroy" do
+ delete("/profile/emails/1").should route_to('profiles/emails#destroy', id: '1')
+ end
+end
+
# profile_avatar DELETE /profile/avatar(.:format) profiles/avatars#destroy
describe Profiles::AvatarsController, "routing" do
it "to #destroy" do
diff --git a/spec/seed_project.tar.gz b/spec/seed_project.tar.gz
index ee8c3f23c70..8d32a927da8 100644
--- a/spec/seed_project.tar.gz
+++ b/spec/seed_project.tar.gz
Binary files differ
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
new file mode 100644
index 00000000000..713aa3e7e74
--- /dev/null
+++ b/spec/services/event_create_service_spec.rb
@@ -0,0 +1,103 @@
+require 'spec_helper'
+
+describe EventCreateService do
+ let(:service) { EventCreateService.new }
+
+ describe 'Issues' do
+ describe :open_issue do
+ let(:issue) { create(:issue) }
+
+ it { service.open_issue(issue, issue.author).should be_true }
+
+ it "should create new event" do
+ expect { service.open_issue(issue, issue.author) }.to change { Event.count }
+ end
+ end
+
+ describe :close_issue do
+ let(:issue) { create(:issue) }
+
+ it { service.close_issue(issue, issue.author).should be_true }
+
+ it "should create new event" do
+ expect { service.close_issue(issue, issue.author) }.to change { Event.count }
+ end
+ end
+
+ describe :reopen_issue do
+ let(:issue) { create(:issue) }
+
+ it { service.reopen_issue(issue, issue.author).should be_true }
+
+ it "should create new event" do
+ expect { service.reopen_issue(issue, issue.author) }.to change { Event.count }
+ end
+ end
+ end
+
+ describe 'Merge Requests' do
+ describe :open_mr do
+ let(:merge_request) { create(:merge_request) }
+
+ it { service.open_mr(merge_request, merge_request.author).should be_true }
+
+ it "should create new event" do
+ expect { service.open_mr(merge_request, merge_request.author) }.to change { Event.count }
+ end
+ end
+
+ describe :close_mr do
+ let(:merge_request) { create(:merge_request) }
+
+ it { service.close_mr(merge_request, merge_request.author).should be_true }
+
+ it "should create new event" do
+ expect { service.close_mr(merge_request, merge_request.author) }.to change { Event.count }
+ end
+ end
+
+ describe :merge_mr do
+ let(:merge_request) { create(:merge_request) }
+
+ it { service.merge_mr(merge_request, merge_request.author).should be_true }
+
+ it "should create new event" do
+ expect { service.merge_mr(merge_request, merge_request.author) }.to change { Event.count }
+ end
+ end
+
+ describe :reopen_mr do
+ let(:merge_request) { create(:merge_request) }
+
+ it { service.reopen_mr(merge_request, merge_request.author).should be_true }
+
+ it "should create new event" do
+ expect { service.reopen_mr(merge_request, merge_request.author) }.to change { Event.count }
+ end
+ end
+ end
+
+ describe 'Milestone' do
+ let(:user) { create :user }
+
+ describe :open_milestone do
+ let(:milestone) { create(:milestone) }
+
+ it { service.open_milestone(milestone, user).should be_true }
+
+ it "should create new event" do
+ expect { service.open_milestone(milestone, user) }.to change { Event.count }
+ end
+ end
+
+ describe :close_mr do
+ let(:milestone) { create(:milestone) }
+
+ it { service.close_milestone(milestone, user).should be_true }
+
+ it "should create new event" do
+ expect { service.close_milestone(milestone, user) }.to change { Event.count }
+ end
+ end
+ end
+end
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
new file mode 100644
index 00000000000..e65a8204c54
--- /dev/null
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe GitTagPushService do
+ let (:user) { create :user }
+ let (:project) { create :project }
+ let (:service) { GitTagPushService.new }
+
+ before do
+ @ref = 'refs/tags/super-tag'
+ @oldrev = 'b98a310def241a6fd9c9a9a3e7934c48e498fe81'
+ @newrev = 'b19a04f53caeebf4fe5ec2327cb83e9253dc91bb'
+ end
+
+ describe 'Git Tag Push Data' do
+ before do
+ service.execute(project, user, @oldrev, @newrev, @ref)
+ @push_data = service.push_data
+ end
+
+ subject { @push_data }
+
+ it { should include(ref: @ref) }
+ it { should include(before: @oldrev) }
+ it { should include(after: @newrev) }
+ it { should include(user_id: user.id) }
+ it { should include(user_name: user.name) }
+ it { should include(project_id: project.id) }
+
+ context 'With repository data' do
+ subject { @push_data[:repository] }
+
+ it { should include(name: project.name) }
+ it { should include(url: project.url_to_repo) }
+ it { should include(description: project.description) }
+ it { should include(homepage: project.web_url) }
+ end
+ end
+
+ describe "Web Hooks" do
+ context "execute web hooks" do
+ it "when pushing tags" do
+ project.should_receive(:execute_hooks)
+ service.execute(project, user, 'oldrev', 'newrev', 'refs/tags/v1.0.0')
+ end
+ end
+ end
+end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 09a5debe1dc..fbd73a7086f 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -16,9 +16,23 @@ describe NotificationService do
end
end
+ describe 'Email' do
+ describe :new_email do
+ let(:email) { create(:email) }
+
+ it { notification.new_email(email).should be_true }
+
+ it 'should send email to email owner' do
+ Notify.should_receive(:new_email_email).with(email.id)
+ notification.new_email(email)
+ end
+ end
+ end
+
describe 'Notes' do
context 'issue note' do
let(:issue) { create(:issue, assignee: create(:user)) }
+ let(:mentioned_issue) { create(:issue, assignee: issue.assignee) }
let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced') }
before do
@@ -37,6 +51,13 @@ describe NotificationService do
notification.new_note(note)
end
+ it 'filters out "mentioned in" notes' do
+ mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author, issue.project)
+
+ Notify.should_not_receive(:note_issue_email)
+ notification.new_note(mentioned_note)
+ end
+
def should_email(user_id)
Notify.should_receive(:note_issue_email).with(user_id, note.id)
end
@@ -124,11 +145,11 @@ describe NotificationService do
end
def should_email(user_id)
- Notify.should_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id)
+ Notify.should_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id)
+ Notify.should_not_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id)
end
end
@@ -188,11 +209,11 @@ describe NotificationService do
end
def should_email(user_id)
- Notify.should_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id)
+ Notify.should_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id)
+ Notify.should_not_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id)
end
end
@@ -220,15 +241,15 @@ describe NotificationService do
should_email(@u_watcher.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
- notification.merge_mr(merge_request)
+ notification.merge_mr(merge_request, @u_disabled)
end
def should_email(user_id)
- Notify.should_receive(:merged_merge_request_email).with(user_id, merge_request.id)
+ Notify.should_receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:merged_merge_request_email).with(user_id, merge_request.id)
+ Notify.should_not_receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
end
end
end
diff --git a/spec/services/projects_create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 0a41832a211..f2a784df103 100644
--- a/spec/services/projects_create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -38,6 +38,27 @@ describe Projects::CreateService do
it { @project.namespace.should == @group }
end
+ context 'wiki_enabled creates repository directory' do
+ context 'wiki_enabled true creates wiki repository directory' do
+ before do
+ @project = create_project(@user, @opts)
+ @path = GollumWiki.new(@project, @user).send(:path_to_repo)
+ end
+
+ it { File.exists?(@path).should be_true }
+ end
+
+ context 'wiki_enabled false does not create wiki repository directory' do
+ before do
+ @opts.merge!(wiki_enabled: false)
+ @project = create_project(@user, @opts)
+ @path = GollumWiki.new(@project, @user).send(:path_to_repo)
+ end
+
+ it { File.exists?(@path).should be_false }
+ end
+ end
+
context 'respect configured visibility setting' do
before(:each) do
@settings = double("settings")
diff --git a/spec/services/project_transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 109b429967e..109b429967e 100644
--- a/spec/services/project_transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
diff --git a/spec/services/projects_update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 1854c0d8233..1854c0d8233 100644
--- a/spec/services/projects_update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 457cb3c0ca3..b467282a5d6 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -1,28 +1,26 @@
require 'spec_helper'
describe 'Search::GlobalService' do
- let(:found_namespace) { create(:namespace, name: 'searchable namespace', path:'another_thing') }
let(:user) { create(:user, namespace: found_namespace) }
- let!(:found_project) { create(:project, name: 'searchable_project', creator_id: user.id, namespace: found_namespace, visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
+ let(:public_user) { create(:user, namespace: public_namespace) }
+ let(:internal_user) { create(:user, namespace: internal_namespace) }
+ let(:found_namespace) { create(:namespace, name: 'searchable namespace', path:'another_thing') }
let(:unfound_namespace) { create(:namespace, name: 'unfound namespace', path: 'yet_something_else') }
- let!(:unfound_project) { create(:project, name: 'unfound_project', creator_id: user.id, namespace: unfound_namespace, visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
+ let(:internal_namespace) { create(:namespace, name: 'searchable internal namespace', path: 'something_internal') }
+ let(:public_namespace) { create(:namespace, name: 'searchable public namespace', path: 'something_public') }
- let(:internal_namespace) { create(:namespace, path: 'something_internal',name: 'searchable internal namespace') }
- let(:internal_user) { create(:user, namespace: internal_namespace) }
- let!(:internal_project) { create(:project, name: 'searchable_internal_project', creator_id: internal_user.id, namespace: internal_namespace, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
-
- let(:public_namespace) { create(:namespace, path: 'something_public',name: 'searchable public namespace') }
- let(:public_user) { create(:user, namespace: public_namespace) }
- let!(:public_project) { create(:project, name: 'searchable_public_project', creator_id: public_user.id, namespace: public_namespace, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+ let!(:found_project) { create(:project, :private, name: 'searchable_project', creator_id: user.id, namespace: found_namespace) }
+ let!(:unfound_project) { create(:project, :private, name: 'unfound_project', creator_id: user.id, namespace: unfound_namespace) }
+ let!(:internal_project) { create(:project, :internal, name: 'searchable_internal_project', creator_id: internal_user.id, namespace: internal_namespace) }
+ let!(:public_project) { create(:project, :public, name: 'searchable_public_project', creator_id: public_user.id, namespace: public_namespace) }
describe '#execute' do
context 'unauthenticated' do
it 'should return public projects only' do
context = Search::GlobalService.new(nil, search: "searchable")
results = context.execute
- results[:projects].should have(1).items
- results[:projects].should include(public_project)
+ results[:projects].should match_array [public_project]
end
end
@@ -30,24 +28,19 @@ describe 'Search::GlobalService' do
it 'should return public, internal and private projects' do
context = Search::GlobalService.new(user, search: "searchable")
results = context.execute
- results[:projects].should have(3).items
- results[:projects].should include(public_project)
- results[:projects].should include(found_project)
- results[:projects].should include(internal_project)
+ results[:projects].should match_array [public_project, found_project, internal_project]
end
it 'should return only public & internal projects' do
context = Search::GlobalService.new(internal_user, search: "searchable")
results = context.execute
- results[:projects].should have(2).items
- results[:projects].should include(internal_project)
- results[:projects].should include(public_project)
+ results[:projects].should match_array [internal_project, public_project]
end
it 'namespace name should be searchable' do
context = Search::GlobalService.new(user, search: "searchable namespace")
results = context.execute
- results[:projects].should == [found_project]
+ results[:projects].should match_array [found_project]
end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index dd008ed02ad..e6b1f816df0 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,63 +1,50 @@
-require 'rubygems'
-require 'spork'
+# This file is copied to spec/ when you run 'rails generate rspec:install'
+ENV["RAILS_ENV"] ||= 'test'
+require File.expand_path("../../config/environment", __FILE__)
-Spork.prefork do
- require 'simplecov' unless ENV['CI']
+require 'simplecov' unless ENV['CI']
- if ENV['TRAVIS']
- require 'coveralls'
- Coveralls.wear!
- end
-
- # This file is copied to spec/ when you run 'rails generate rspec:install'
- ENV["RAILS_ENV"] ||= 'test'
- require File.expand_path("../../config/environment", __FILE__)
- require 'rspec/rails'
- require 'capybara/rails'
- require 'capybara/rspec'
- require 'webmock/rspec'
- require 'email_spec'
- require 'sidekiq/testing/inline'
- require 'capybara/poltergeist'
-
- # Loading more in this block will cause your tests to run faster. However,
-
- # if you change any configuration or code from libraries loaded here, you'll
- # need to restart spork for it take effect.
- Capybara.javascript_driver = :poltergeist
- Capybara.default_wait_time = 10
-
- # Requires supporting ruby files with custom matchers and macros, etc,
- # in spec/support/ and its subdirectories.
- Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
-
- WebMock.disable_net_connect!(allow_localhost: true)
-
- RSpec.configure do |config|
- config.mock_with :rspec
-
- config.include LoginHelpers, type: :feature
- config.include LoginHelpers, type: :request
- config.include FactoryGirl::Syntax::Methods
- config.include Devise::TestHelpers, type: :controller
-
- config.include TestEnv
-
- # If you're not using ActiveRecord, or you'd prefer not to run each of your
- # examples within a transaction, remove the following line or assign false
- # instead of true.
- config.use_transactional_fixtures = false
-
- config.before(:suite) do
- TestEnv.init(observers: false, init_repos: true, repos: false)
- end
- config.before(:each) do
- TestEnv.setup_stubs
- end
- end
+if ENV['TRAVIS']
+ require 'coveralls'
+ Coveralls.wear!
end
-Spork.each_run do
- # This code will be run each time you run your specs.
+require 'rspec/rails'
+require 'capybara/rails'
+require 'capybara/rspec'
+require 'webmock/rspec'
+require 'email_spec'
+require 'sidekiq/testing/inline'
+require 'capybara/poltergeist'
+
+Capybara.javascript_driver = :poltergeist
+Capybara.default_wait_time = 10
+
+# Requires supporting ruby files with custom matchers and macros, etc,
+# in spec/support/ and its subdirectories.
+Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
+
+WebMock.disable_net_connect!(allow_localhost: true)
+RSpec.configure do |config|
+ config.mock_with :rspec
+
+ config.include LoginHelpers, type: :feature
+ config.include LoginHelpers, type: :request
+ config.include FactoryGirl::Syntax::Methods
+ config.include Devise::TestHelpers, type: :controller
+
+ config.include TestEnv
+
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
+ # examples within a transaction, remove the following line or assign false
+ # instead of true.
+ config.use_transactional_fixtures = false
+
+ config.before(:suite) do
+ TestEnv.init(observers: false, init_repos: true, repos: false)
+ end
+ config.before(:each) do
+ TestEnv.setup_stubs
+ end
end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 43aec1cd43d..d237f7ad094 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -29,7 +29,6 @@ module TestEnv
disable_mailer if opts[:mailer] == false
setup_stubs
-
clear_test_repo_dir if opts[:init_repos] == true
setup_test_repos(opts) if opts[:repos] == true
end
@@ -91,7 +90,7 @@ module TestEnv
size: 12.45
)
- ActivityObserver.any_instance.stub(
+ BaseObserver.any_instance.stub(
current_user: double("current_user", id: 1)
)
end
@@ -104,10 +103,12 @@ module TestEnv
def reset_satellite_dir
setup_stubs
- FileUtils.cd(seed_satellite_path) do
- `git reset --hard --quiet`
- `git clean -fx`
- `git checkout --quiet origin/master`
+ [
+ %W(git reset --hard --quiet),
+ %W(git clean -fx --quiet),
+ %W(git checkout --quiet origin/master)
+ ].each do |git_cmd|
+ system(*git_cmd, chdir: seed_satellite_path)
end
end
@@ -117,7 +118,7 @@ module TestEnv
repo = repo(namespace, name)
# Symlink tmp/repositories/gitlabhq to tmp/test-git-base-path/gitlabhq
- system("ln -s -f #{seed_repo_path()} #{repo}")
+ FileUtils.ln_sf(seed_repo_path, repo)
create_satellite(repo, namespace, name)
end
@@ -163,8 +164,7 @@ module TestEnv
def clear_test_repo_dir
setup_stubs
- # Use tmp dir for FS manipulations
- repos_path = testing_path()
+
# Remove tmp/test-git-base-path
FileUtils.rm_rf Gitlab.config.gitlab_shell.repos_path
@@ -181,12 +181,11 @@ module TestEnv
# Symlink tmp/satellite/gitlabhq to tmp/test-git-base-path/satellite/gitlabhq, create the directory if it doesn't exist already
satellite_dir = File.dirname(satellite_repo)
FileUtils.mkdir_p(satellite_dir) unless File.exists?(satellite_dir)
- system("ln -s -f #{seed_satellite_path} #{satellite_repo}")
+ FileUtils.ln_sf(seed_satellite_path, satellite_repo)
end
def create_temp_repo(path)
FileUtils.mkdir_p path
- command = "git init --quiet --bare #{path};"
- system(command)
+ system(*%W(git init --quiet --bare -- #{path}))
end
end
diff --git a/spec/support/valid_commit.rb b/spec/support/valid_commit.rb
index 8094b679e99..98bc59b573f 100644
--- a/spec/support/valid_commit.rb
+++ b/spec/support/valid_commit.rb
@@ -2,6 +2,7 @@ module ValidCommit
ID = "8470d70da67355c9c009e4401746b1d5410af2e3"
MESSAGE = "notes controller refactored"
AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
+ AUTHOR_EMAIL = "dmitriy.zaporozhets@gmail.com"
FILES = [".foreman", ".gitignore", ".rails_footnotes", ".rspec", ".travis.yml", "CHANGELOG", "Gemfile", "Gemfile.lock", "LICENSE", "Procfile", "Procfile.production", "README.md", "Rakefile", "VERSION", "app", "config.ru", "config", "db", "doc", "lib", "log", "public", "resque.sh", "script", "spec", "vendor"]
FILES_COUNT = 26
diff --git a/spec/support/valid_commit_with_alt_email.rb b/spec/support/valid_commit_with_alt_email.rb
new file mode 100644
index 00000000000..d6e364c41f1
--- /dev/null
+++ b/spec/support/valid_commit_with_alt_email.rb
@@ -0,0 +1,6 @@
+module ValidCommitWithAltEmail
+ ID = "1e689bfba39525ead225eaf611948cfbe8ac34cf"
+ MESSAGE = "fixed notes logic"
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
+ AUTHOR_EMAIL = "dzaporozhets@sphereconsultinginc.com"
+end \ No newline at end of file
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index a5541bee876..71a45eb2fa6 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -32,10 +32,10 @@ describe 'gitlab:app namespace rake task' do
Rake::Task["gitlab:shell:setup"].stub invoke: true
end
- let(:gitlab_version) { %x{git rev-parse HEAD}.gsub(/\n/,"") }
+ let(:gitlab_version) { Gitlab::VERSION }
it 'should fail on mismatch' do
- YAML.stub load_file: {gitlab_version: gitlab_version.reverse}
+ YAML.stub load_file: {gitlab_version: "not #{gitlab_version}" }
expect { run_rake_task }.to raise_error SystemExit
end