summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore8
-rw-r--r--.rspec2
-rw-r--r--.travis.yml5
-rw-r--r--CHANGELOG219
-rw-r--r--CONTRIBUTING.md75
-rw-r--r--Gemfile122
-rw-r--r--Gemfile.lock668
-rw-r--r--MAINTENANCE.md23
-rw-r--r--PROCESS.md105
-rw-r--r--Procfile2
-rw-r--r--README.md175
-rw-r--r--ROADMAP.md12
-rw-r--r--VERSION2
-rw-r--r--app/assets/fonts/OFL.txt92
-rw-r--r--app/assets/fonts/YanoneKaffeesatz-Light.ttfbin77296 -> 0 bytes
-rw-r--r--app/assets/images/dark-scheme-preview.png (renamed from app/assets/images/dark.png)bin16935 -> 16935 bytes
-rw-r--r--app/assets/images/login-logo.pngbin1429 -> 0 bytes
-rw-r--r--app/assets/images/logo-black.pngbin0 -> 3080 bytes
-rw-r--r--app/assets/images/logo-white.pngbin0 -> 5722 bytes
-rw-r--r--app/assets/images/logo_dark.pngbin2589 -> 0 bytes
-rw-r--r--app/assets/images/logo_white.pngbin1920 -> 0 bytes
-rw-r--r--app/assets/images/merge.pngbin593 -> 0 bytes
-rw-r--r--app/assets/images/monokai-scheme-preview.pngbin0 -> 6651 bytes
-rw-r--r--app/assets/images/solarized-dark-scheme-preview.pngbin0 -> 16320 bytes
-rw-r--r--app/assets/images/white-scheme-preview.png (renamed from app/assets/images/white.png)bin17161 -> 17161 bytes
-rw-r--r--app/assets/javascripts/activities.js.coffee31
-rw-r--r--app/assets/javascripts/admin.js.coffee55
-rw-r--r--app/assets/javascripts/api.js.coffee54
-rw-r--r--app/assets/javascripts/application.js6
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.coffee12
-rw-r--r--app/assets/javascripts/blob.js.coffee24
-rw-r--r--app/assets/javascripts/branch-graph.js.coffee326
-rw-r--r--app/assets/javascripts/chart.js.coffee21
-rw-r--r--app/assets/javascripts/commit.js.coffee6
-rw-r--r--app/assets/javascripts/commits.js.coffee7
-rw-r--r--app/assets/javascripts/dashboard.js.coffee60
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee51
-rw-r--r--app/assets/javascripts/extensions/jquery.js.coffee13
-rw-r--r--app/assets/javascripts/flash.js.coffee15
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee50
-rw-r--r--app/assets/javascripts/groups.js.coffee6
-rw-r--r--app/assets/javascripts/issues.js80
-rw-r--r--app/assets/javascripts/issues.js.coffee72
-rw-r--r--app/assets/javascripts/lib/jquery.timeago.js181
-rw-r--r--app/assets/javascripts/lib/md5.js211
-rw-r--r--app/assets/javascripts/lib/utf8_encode.js70
-rw-r--r--app/assets/javascripts/main.js.coffee52
-rw-r--r--app/assets/javascripts/merge_requests.js.coffee9
-rw-r--r--app/assets/javascripts/milestones.js.coffee14
-rw-r--r--app/assets/javascripts/network.js.coffee11
-rw-r--r--app/assets/javascripts/notes.js312
-rw-r--r--app/assets/javascripts/pager.js.coffee1
-rw-r--r--app/assets/javascripts/profile.js.coffee12
-rw-r--r--app/assets/javascripts/project.js.coffee42
-rw-r--r--app/assets/javascripts/project_import.js.coffee7
-rw-r--r--app/assets/javascripts/projects.js.coffee20
-rw-r--r--app/assets/javascripts/search_autocomplete.js.coffee8
-rw-r--r--app/assets/javascripts/shortcuts.js.coffee11
-rw-r--r--app/assets/javascripts/stat_graph.js.coffee6
-rw-r--r--app/assets/javascripts/stat_graph_contributors.js.coffee84
-rw-r--r--app/assets/javascripts/stat_graph_contributors_graph.js.coffee176
-rw-r--r--app/assets/javascripts/stat_graph_contributors_util.js.coffee93
-rw-r--r--app/assets/javascripts/team_members.js.coffee6
-rw-r--r--app/assets/javascripts/tree.js.coffee91
-rw-r--r--app/assets/javascripts/users_select.js.coffee37
-rw-r--r--app/assets/javascripts/wall.js.coffee85
-rw-r--r--app/assets/javascripts/wikis.js.coffee12
-rw-r--r--app/assets/stylesheets/application.scss9
-rw-r--r--app/assets/stylesheets/common.scss337
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap.scss44
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/avatar.scss30
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/blocks.scss93
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/buttons.scss138
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/common.scss83
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/files.scss29
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/fonts.scss5
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/forms.scss51
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/lists.scss24
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/mixins.scss51
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/nav.scss29
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/tables.scss63
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/typography.scss56
-rw-r--r--app/assets/stylesheets/highlight/dark.scss6
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss89
-rw-r--r--app/assets/stylesheets/highlight/solarized_dark.scss80
-rw-r--r--app/assets/stylesheets/highlight/white.scss3
-rw-r--r--app/assets/stylesheets/ref_select.scss90
-rw-r--r--app/assets/stylesheets/sections/admin.scss18
-rw-r--r--app/assets/stylesheets/sections/commits.scss205
-rw-r--r--app/assets/stylesheets/sections/dashboard.scss102
-rw-r--r--app/assets/stylesheets/sections/events.scss103
-rw-r--r--app/assets/stylesheets/sections/graph.scss35
-rw-r--r--app/assets/stylesheets/sections/header.scss177
-rw-r--r--app/assets/stylesheets/sections/issues.scss56
-rw-r--r--app/assets/stylesheets/sections/login.scss13
-rw-r--r--app/assets/stylesheets/sections/merge_requests.scss79
-rw-r--r--app/assets/stylesheets/sections/nav.scss139
-rw-r--r--app/assets/stylesheets/sections/notes.scss78
-rw-r--r--app/assets/stylesheets/sections/profile.scss24
-rw-r--r--app/assets/stylesheets/sections/projects.scss118
-rw-r--r--app/assets/stylesheets/sections/snippets.scss11
-rw-r--r--app/assets/stylesheets/sections/stat_graph.scss50
-rw-r--r--app/assets/stylesheets/sections/themes.scss13
-rw-r--r--app/assets/stylesheets/sections/tree.scss42
-rw-r--r--app/assets/stylesheets/sections/votes.scss7
-rw-r--r--app/assets/stylesheets/sections/wall.scss55
-rw-r--r--app/assets/stylesheets/sections/wiki.scss6
-rw-r--r--app/assets/stylesheets/selects.scss153
-rw-r--r--app/assets/stylesheets/themes/ui_basic.scss6
-rw-r--r--app/assets/stylesheets/themes/ui_color.scss9
-rw-r--r--app/assets/stylesheets/themes/ui_gray.scss9
-rw-r--r--app/assets/stylesheets/themes/ui_mars.scss59
-rw-r--r--app/assets/stylesheets/themes/ui_modern.scss9
-rw-r--r--app/contexts/commit_load_context.rb4
-rw-r--r--app/contexts/filter_context.rb4
-rw-r--r--app/contexts/issues/bulk_update_context.rb39
-rw-r--r--app/contexts/issues/list_context.rb36
-rw-r--r--app/contexts/issues_bulk_update_context.rb24
-rw-r--r--app/contexts/issues_list_context.rb29
-rw-r--r--app/contexts/merge_requests_load_context.rb16
-rw-r--r--app/contexts/notes/create_context.rb2
-rw-r--r--app/contexts/notes/load_context.rb13
-rw-r--r--app/contexts/projects/create_context.rb45
-rw-r--r--app/contexts/projects/fork_context.rb44
-rw-r--r--app/contexts/projects/transfer_context.rb22
-rw-r--r--app/contexts/projects/update_context.rb18
-rw-r--r--app/contexts/search_context.rb21
-rw-r--r--app/contexts/test_hook_context.rb3
-rw-r--r--app/controllers/admin/background_jobs_controller.rb4
-rw-r--r--app/controllers/admin/dashboard_controller.rb3
-rw-r--r--app/controllers/admin/groups_controller.rb45
-rw-r--r--app/controllers/admin/projects/application_controller.rb11
-rw-r--r--app/controllers/admin/projects/members_controller.rb32
-rw-r--r--app/controllers/admin/projects_controller.rb39
-rw-r--r--app/controllers/admin/resque_controller.rb4
-rw-r--r--app/controllers/admin/teams/application_controller.rb11
-rw-r--r--app/controllers/admin/teams/members_controller.rb41
-rw-r--r--app/controllers/admin/teams/projects_controller.rb41
-rw-r--r--app/controllers/admin/teams_controller.rb59
-rw-r--r--app/controllers/admin/users_controller.rb85
-rw-r--r--app/controllers/application_controller.rb52
-rw-r--r--app/controllers/blob_controller.rb24
-rw-r--r--app/controllers/compare_controller.rb25
-rw-r--r--app/controllers/dashboard_controller.rb31
-rw-r--r--app/controllers/deploy_keys_controller.rb39
-rw-r--r--app/controllers/files_controller.rb17
-rw-r--r--app/controllers/graph_controller.rb28
-rw-r--r--app/controllers/groups_controller.rb62
-rw-r--r--app/controllers/help_controller.rb14
-rw-r--r--app/controllers/merge_requests_controller.rb145
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb32
-rw-r--r--app/controllers/profiles/groups_controller.rb24
-rw-r--r--app/controllers/profiles/keys_controller.rb (renamed from app/controllers/keys_controller.rb)16
-rw-r--r--app/controllers/profiles/notifications_controller.rb26
-rw-r--r--app/controllers/profiles/passwords_controller.rb38
-rw-r--r--app/controllers/profiles_controller.rb24
-rw-r--r--app/controllers/project_resource_controller.rb4
-rw-r--r--app/controllers/projects/application_controller.rb23
-rw-r--r--app/controllers/projects/blame_controller.rb14
-rw-r--r--app/controllers/projects/blob_controller.rb (renamed from app/controllers/blame_controller.rb)7
-rw-r--r--app/controllers/projects/branches_controller.rb40
-rw-r--r--app/controllers/projects/commit_controller.rb (renamed from app/controllers/commit_controller.rb)9
-rw-r--r--app/controllers/projects/commits_controller.rb (renamed from app/controllers/commits_controller.rb)3
-rw-r--r--app/controllers/projects/compare_controller.rb27
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb61
-rw-r--r--app/controllers/projects/edit_tree_controller.rb (renamed from app/controllers/tree_controller.rb)30
-rw-r--r--app/controllers/projects/graphs_controller.rb25
-rw-r--r--app/controllers/projects/hooks_controller.rb (renamed from app/controllers/hooks_controller.rb)7
-rw-r--r--app/controllers/projects/issues_controller.rb (renamed from app/controllers/issues_controller.rb)57
-rw-r--r--app/controllers/projects/labels_controller.rb (renamed from app/controllers/labels_controller.rb)10
-rw-r--r--app/controllers/projects/merge_requests_controller.rb200
-rw-r--r--app/controllers/projects/milestones_controller.rb (renamed from app/controllers/milestones_controller.rb)10
-rw-r--r--app/controllers/projects/network_controller.rb19
-rw-r--r--app/controllers/projects/notes_controller.rb (renamed from app/controllers/notes_controller.rb)31
-rw-r--r--app/controllers/projects/protected_branches_controller.rb (renamed from app/controllers/protected_branches_controller.rb)2
-rw-r--r--app/controllers/projects/raw_controller.rb33
-rw-r--r--app/controllers/projects/refs_controller.rb43
-rw-r--r--app/controllers/projects/repositories_controller.rb (renamed from app/controllers/repositories_controller.rb)19
-rw-r--r--app/controllers/projects/services_controller.rb39
-rw-r--r--app/controllers/projects/snippets_controller.rb89
-rw-r--r--app/controllers/projects/tags_controller.rb36
-rw-r--r--app/controllers/projects/team_members_controller.rb (renamed from app/controllers/team_members_controller.rb)21
-rw-r--r--app/controllers/projects/teams_controller.rb27
-rw-r--r--app/controllers/projects/tree_controller.rb17
-rw-r--r--app/controllers/projects/walls_controller.rb20
-rw-r--r--app/controllers/projects/wikis_controller.rb95
-rw-r--r--app/controllers/projects_controller.rb103
-rw-r--r--app/controllers/public/projects_controller.rb5
-rw-r--r--app/controllers/refs_controller.rb69
-rw-r--r--app/controllers/registrations_controller.rb12
-rw-r--r--app/controllers/search_controller.rb6
-rw-r--r--app/controllers/services_controller.rb37
-rw-r--r--app/controllers/snippets_controller.rb76
-rw-r--r--app/controllers/teams/application_controller.rb13
-rw-r--r--app/controllers/teams/members_controller.rb49
-rw-r--r--app/controllers/teams/projects_controller.rb57
-rw-r--r--app/controllers/teams_controller.rb76
-rw-r--r--app/controllers/users_controller.rb4
-rw-r--r--app/controllers/users_groups_controller.rb41
-rw-r--r--app/controllers/wikis_controller.rb59
-rw-r--r--app/decorators/application_decorator.rb28
-rw-r--r--app/decorators/commit_decorator.rb92
-rw-r--r--app/decorators/event_decorator.rb44
-rw-r--r--app/decorators/tree_decorator.rb33
-rw-r--r--app/decorators/user_decorator.rb15
-rw-r--r--app/helpers/admin/teams/members_helper.rb5
-rw-r--r--app/helpers/admin/teams/projects_helper.rb5
-rw-r--r--app/helpers/application_helper.rb143
-rw-r--r--app/helpers/commits_helper.rb145
-rw-r--r--app/helpers/compare_helper.rb13
-rw-r--r--app/helpers/dashboard_helper.rb13
-rw-r--r--app/helpers/events_helper.rb87
-rw-r--r--app/helpers/gitlab_markdown_helper.rb8
-rw-r--r--app/helpers/graph_helper.rb16
-rw-r--r--app/helpers/groups_helper.rb16
-rw-r--r--app/helpers/issues_helper.rb67
-rw-r--r--app/helpers/labels_helper.rb28
-rw-r--r--app/helpers/merge_requests_helper.rb37
-rw-r--r--app/helpers/namespaces_helper.rb16
-rw-r--r--app/helpers/notes_helper.rb10
-rw-r--r--app/helpers/notifications_helper.rb13
-rw-r--r--app/helpers/oauth_helper.rb19
-rw-r--r--app/helpers/profile_helper.rb12
-rw-r--r--app/helpers/projects_helper.rb101
-rw-r--r--app/helpers/snippets_helper.rb8
-rw-r--r--app/helpers/tab_helper.rb10
-rw-r--r--app/helpers/tree_helper.rb67
-rw-r--r--app/helpers/user_teams_helper.rb26
-rw-r--r--app/mailers/emails/groups.rb11
-rw-r--r--app/mailers/emails/issues.rb33
-rw-r--r--app/mailers/emails/merge_requests.rb66
-rw-r--r--app/mailers/emails/notes.rb30
-rw-r--r--app/mailers/emails/profile.rb15
-rw-r--r--app/mailers/emails/projects.rb17
-rw-r--r--app/mailers/notify.rb118
-rw-r--r--app/models/ability.rb88
-rw-r--r--app/models/campfire_service.rb78
-rw-r--r--app/models/commit.rb228
-rw-r--r--app/models/concerns/internal_id.rb17
-rw-r--r--app/models/concerns/issuable.rb39
-rw-r--r--app/models/concerns/mentionable.rb97
-rw-r--r--app/models/concerns/notifiable.rb15
-rw-r--r--app/models/deploy_key.rb20
-rw-r--r--app/models/deploy_keys_project.rb22
-rw-r--r--app/models/event.rb81
-rw-r--r--app/models/forked_project_link.rb19
-rw-r--r--app/models/gitlab_ci_service.rb29
-rw-r--r--app/models/gollum_wiki.rb119
-rw-r--r--app/models/group.rb58
-rw-r--r--app/models/hipchat_service.rb75
-rw-r--r--app/models/issue.rb45
-rw-r--r--app/models/key.rb80
-rw-r--r--app/models/merge_request.rb296
-rw-r--r--app/models/milestone.rb41
-rw-r--r--app/models/namespace.rb78
-rw-r--r--app/models/network/commit.rb37
-rw-r--r--app/models/network/graph.rb273
-rw-r--r--app/models/note.rb120
-rw-r--r--app/models/notification.rb39
-rw-r--r--app/models/personal_snippet.rb19
-rw-r--r--app/models/pivotaltracker_service.rb59
-rw-r--r--app/models/project.rb384
-rw-r--r--app/models/project_snippet.rb28
-rw-r--r--app/models/project_team.rb73
-rw-r--r--app/models/protected_branch.rb2
-rw-r--r--app/models/repository.rb205
-rw-r--r--app/models/service.rb29
-rw-r--r--app/models/snippet.rb17
-rw-r--r--app/models/system_hook.rb9
-rw-r--r--app/models/tree.rb28
-rw-r--r--app/models/user.rb302
-rw-r--r--app/models/user_team.rb109
-rw-r--r--app/models/user_team_project_relationship.rb40
-rw-r--r--app/models/user_team_user_relationship.rb32
-rw-r--r--app/models/users_group.rb46
-rw-r--r--app/models/users_project.rb56
-rw-r--r--app/models/web_hook.rb6
-rw-r--r--app/models/wiki.rb55
-rw-r--r--app/models/wiki_page.rb185
-rw-r--r--app/observers/activity_observer.rb49
-rw-r--r--app/observers/base_observer.rb17
-rw-r--r--app/observers/issue_observer.rb43
-rw-r--r--app/observers/key_observer.rb8
-rw-r--r--app/observers/merge_request_observer.rb63
-rw-r--r--app/observers/note_observer.rb40
-rw-r--r--app/observers/project_activity_cache_observer.rb8
-rw-r--r--app/observers/project_observer.rb42
-rw-r--r--app/observers/system_hook_observer.rb64
-rw-r--r--app/observers/user_observer.rb19
-rw-r--r--app/observers/users_group_observer.rb9
-rw-r--r--app/observers/users_project_observer.rb9
-rw-r--r--app/services/git_push_service.rb194
-rw-r--r--app/services/notification_service.rb251
-rw-r--r--app/services/project_transfer_service.rb42
-rw-r--r--app/services/system_hooks_service.rb62
-rw-r--r--app/uploaders/attachment_uploader.rb14
-rw-r--r--app/views/admin/background_jobs/show.html.haml (renamed from app/views/admin/resque/show.html.haml)2
-rw-r--r--app/views/admin/dashboard/index.html.haml22
-rw-r--r--app/views/admin/groups/edit.html.haml21
-rw-r--r--app/views/admin/groups/index.html.haml60
-rw-r--r--app/views/admin/groups/new.html.haml20
-rw-r--r--app/views/admin/groups/show.html.haml185
-rw-r--r--app/views/admin/hooks/_data_ex.html.erb37
-rw-r--r--app/views/admin/hooks/index.html.haml10
-rw-r--r--app/views/admin/logs/show.html.haml26
-rw-r--r--app/views/admin/projects/_form.html.haml77
-rw-r--r--app/views/admin/projects/edit.html.haml3
-rw-r--r--app/views/admin/projects/index.html.haml26
-rw-r--r--app/views/admin/projects/members/_form.html.haml16
-rw-r--r--app/views/admin/projects/members/edit.html.haml8
-rw-r--r--app/views/admin/projects/show.html.haml222
-rw-r--r--app/views/admin/teams/edit.html.haml23
-rw-r--r--app/views/admin/teams/index.html.haml38
-rw-r--r--app/views/admin/teams/members/_form.html.haml20
-rw-r--r--app/views/admin/teams/members/edit.html.haml16
-rw-r--r--app/views/admin/teams/members/new.html.haml29
-rw-r--r--app/views/admin/teams/new.html.haml19
-rw-r--r--app/views/admin/teams/projects/_form.html.haml16
-rw-r--r--app/views/admin/teams/projects/edit.html.haml16
-rw-r--r--app/views/admin/teams/projects/new.html.haml23
-rw-r--r--app/views/admin/teams/show.html.haml101
-rw-r--r--app/views/admin/users/_form.html.haml95
-rw-r--r--app/views/admin/users/edit.html.haml4
-rw-r--r--app/views/admin/users/index.html.haml18
-rw-r--r--app/views/admin/users/new.html.haml2
-rw-r--r--app/views/admin/users/show.html.haml186
-rw-r--r--app/views/blame/_head.html.haml7
-rw-r--r--app/views/commit/show.html.haml17
-rw-r--r--app/views/commits/_commit.html.haml21
-rw-r--r--app/views/commits/_commit_box.html.haml50
-rw-r--r--app/views/commits/_commits.html.haml6
-rw-r--r--app/views/commits/_diffs.html.haml49
-rw-r--r--app/views/compare/_form.html.haml39
-rw-r--r--app/views/compare/index.html.haml7
-rw-r--r--app/views/compare/show.html.haml17
-rw-r--r--app/views/dashboard/_activities.html.haml7
-rw-r--r--app/views/dashboard/_filter.html.haml33
-rw-r--r--app/views/dashboard/_groups.html.haml25
-rw-r--r--app/views/dashboard/_project.html.haml12
-rw-r--r--app/views/dashboard/_projects.html.haml38
-rw-r--r--app/views/dashboard/_sidebar.html.haml33
-rw-r--r--app/views/dashboard/_teams.html.haml20
-rw-r--r--app/views/dashboard/_zero_authorized_projects.html.haml48
-rw-r--r--app/views/dashboard/issues.html.haml25
-rw-r--r--app/views/dashboard/merge_requests.html.haml10
-rw-r--r--app/views/dashboard/projects.html.haml121
-rw-r--r--app/views/dashboard/show.atom.builder11
-rw-r--r--app/views/dashboard/show.html.haml5
-rw-r--r--app/views/deploy_keys/_show.html.haml12
-rw-r--r--app/views/deploy_keys/index.html.haml17
-rw-r--r--app/views/deploy_keys/new.html.haml6
-rw-r--r--app/views/devise/confirmations/new.html.erb4
-rw-r--r--app/views/devise/mailer/confirmation_instructions.html.erb2
-rw-r--r--app/views/devise/mailer/reset_password_instructions.html.erb2
-rw-r--r--app/views/devise/mailer/unlock_instructions.html.erb2
-rw-r--r--app/views/devise/passwords/edit.html.haml1
-rw-r--r--app/views/devise/passwords/new.html.erb9
-rw-r--r--app/views/devise/passwords/new.html.haml10
-rw-r--r--app/views/devise/registrations/edit.html.erb6
-rw-r--r--app/views/devise/registrations/new.html.haml3
-rw-r--r--app/views/devise/sessions/_new_base.html.haml14
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml32
-rw-r--r--app/views/devise/sessions/_oauth_providers.html.haml11
-rw-r--r--app/views/devise/sessions/new.html.haml56
-rw-r--r--app/views/devise/shared/_links.erb4
-rw-r--r--app/views/devise/unlocks/new.html.erb4
-rw-r--r--app/views/errors/access_denied.html.haml2
-rw-r--r--app/views/errors/encoding.html.haml2
-rw-r--r--app/views/errors/git_not_found.html.haml2
-rw-r--r--app/views/errors/not_found.html.haml2
-rw-r--r--app/views/events/_commit.html.haml11
-rw-r--r--app/views/events/_event.html.haml18
-rw-r--r--app/views/events/_event_last_push.html.haml9
-rw-r--r--app/views/events/_event_push.atom.haml10
-rw-r--r--app/views/events/event/_common.html.haml8
-rw-r--r--app/views/events/event/_note.html.haml30
-rw-r--r--app/views/events/event/_push.html.haml6
-rw-r--r--app/views/graph/_head.html.haml16
-rw-r--r--app/views/graph/show.html.haml17
-rw-r--r--app/views/groups/_filter.html.haml8
-rw-r--r--app/views/groups/_new_group_member.html.haml24
-rw-r--r--app/views/groups/_new_member.html.haml18
-rw-r--r--app/views/groups/_people_filter.html.haml14
-rw-r--r--app/views/groups/_projects.html.haml25
-rw-r--r--app/views/groups/edit.html.haml119
-rw-r--r--app/views/groups/issues.html.haml28
-rw-r--r--app/views/groups/members.html.haml20
-rw-r--r--app/views/groups/merge_requests.html.haml11
-rw-r--r--app/views/groups/new.html.haml37
-rw-r--r--app/views/groups/people.html.haml20
-rw-r--r--app/views/groups/search.html.haml9
-rw-r--r--app/views/groups/show.atom.builder11
-rw-r--r--app/views/groups/show.html.haml24
-rw-r--r--app/views/help/_api_layout.html.haml13
-rw-r--r--app/views/help/_layout.html.haml2
-rw-r--r--app/views/help/_shortcuts.html.haml30
-rw-r--r--app/views/help/api.html.haml119
-rw-r--r--app/views/help/index.html.haml33
-rw-r--r--app/views/help/markdown.html.haml129
-rw-r--r--app/views/help/permissions.html.haml270
-rw-r--r--app/views/help/public_access.html.haml5
-rw-r--r--app/views/help/raketasks.html.haml33
-rw-r--r--app/views/help/shortcuts.js.haml3
-rw-r--r--app/views/help/ssh.html.haml3
-rw-r--r--app/views/help/system_hooks.html.haml3
-rw-r--r--app/views/help/web_hooks.html.haml5
-rw-r--r--app/views/help/workflow.html.haml3
-rw-r--r--app/views/issues/_filter.html.haml20
-rw-r--r--app/views/issues/_form.html.haml85
-rw-r--r--app/views/issues/_issues.html.haml13
-rw-r--r--app/views/issues/index.html.haml50
-rw-r--r--app/views/issues/index.js.haml2
-rw-r--r--app/views/issues/show.html.haml59
-rw-r--r--app/views/kaminari/admin/_first_page.html.haml9
-rw-r--r--app/views/kaminari/admin/_gap.html.haml9
-rw-r--r--app/views/kaminari/admin/_last_page.html.haml9
-rw-r--r--app/views/kaminari/admin/_next_page.html.haml9
-rw-r--r--app/views/kaminari/admin/_page.html.haml10
-rw-r--r--app/views/kaminari/admin/_paginator.html.haml17
-rw-r--r--app/views/kaminari/admin/_prev_page.html.haml9
-rw-r--r--app/views/kaminari/gitlab/_gap.html.haml5
-rw-r--r--app/views/kaminari/gitlab/_next_page.html.haml2
-rw-r--r--app/views/kaminari/gitlab/_page.html.haml4
-rw-r--r--app/views/kaminari/gitlab/_paginator.html.haml17
-rw-r--r--app/views/kaminari/gitlab/_prev_page.html.haml2
-rw-r--r--app/views/keys/_form.html.haml24
-rw-r--r--app/views/keys/_show.html.haml12
-rw-r--r--app/views/keys/edit.html.haml7
-rw-r--r--app/views/keys/index.html.haml22
-rw-r--r--app/views/keys/show.html.haml14
-rw-r--r--app/views/labels/_label.html.haml9
-rw-r--r--app/views/labels/index.html.haml14
-rw-r--r--app/views/layouts/_flash.html.haml11
-rw-r--r--app/views/layouts/_google_analytics.html.haml10
-rw-r--r--app/views/layouts/_head.html.haml3
-rw-r--r--app/views/layouts/_head_panel.html.haml36
-rw-r--r--app/views/layouts/_init_auto_complete.html.haml19
-rw-r--r--app/views/layouts/_page_title.html.haml4
-rw-r--r--app/views/layouts/_public_head_panel.html.haml22
-rw-r--r--app/views/layouts/_search.html.haml17
-rw-r--r--app/views/layouts/admin.html.haml26
-rw-r--r--app/views/layouts/application.html.haml27
-rw-r--r--app/views/layouts/devise.html.haml11
-rw-r--r--app/views/layouts/errors.html.haml2
-rw-r--r--app/views/layouts/group.html.haml30
-rw-r--r--app/views/layouts/nav/_admin.html.haml17
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml18
-rw-r--r--app/views/layouts/nav/_group.html.haml20
-rw-r--r--app/views/layouts/nav/_profile.html.haml19
-rw-r--r--app/views/layouts/nav/_project.html.haml50
-rw-r--r--app/views/layouts/navless.html.haml10
-rw-r--r--app/views/layouts/notify.html.haml40
-rw-r--r--app/views/layouts/profile.html.haml22
-rw-r--r--app/views/layouts/project_resource.html.haml45
-rw-r--r--app/views/layouts/project_settings.html.haml20
-rw-r--r--app/views/layouts/projects.html.haml15
-rw-r--r--app/views/layouts/public.html.haml22
-rw-r--r--app/views/layouts/public_projects.html.haml9
-rw-r--r--app/views/layouts/search.html.haml10
-rw-r--r--app/views/layouts/user_team.html.haml38
-rw-r--r--app/views/merge_requests/_filter.html.haml20
-rw-r--r--app/views/merge_requests/_form.html.haml81
-rw-r--r--app/views/merge_requests/_merge_request.html.haml32
-rw-r--r--app/views/merge_requests/_show.html.haml39
-rw-r--r--app/views/merge_requests/branch_from.js.haml2
-rw-r--r--app/views/merge_requests/branch_to.js.haml2
-rw-r--r--app/views/merge_requests/edit.html.haml4
-rw-r--r--app/views/merge_requests/index.html.haml35
-rw-r--r--app/views/merge_requests/new.html.haml3
-rw-r--r--app/views/merge_requests/show/_diffs.html.haml10
-rw-r--r--app/views/merge_requests/show/_how_to_merge.html.haml24
-rw-r--r--app/views/merge_requests/show/_mr_box.html.haml34
-rw-r--r--app/views/milestones/show.html.haml94
-rw-r--r--app/views/notes/_diff_notes_with_reply.html.haml11
-rw-r--r--app/views/notes/_note.html.haml37
-rw-r--r--app/views/notes/_notes_with_form.html.haml11
-rw-r--r--app/views/notes/_reversed_notes_with_form.html.haml12
-rw-r--r--app/views/notes/index.js.haml15
-rw-r--r--app/views/notify/_note_message.html.haml6
-rw-r--r--app/views/notify/closed_issue_email.html.haml5
-rw-r--r--app/views/notify/closed_issue_email.text.haml3
-rw-r--r--app/views/notify/closed_merge_request_email.html.haml9
-rw-r--r--app/views/notify/closed_merge_request_email.text.haml8
-rw-r--r--app/views/notify/group_access_granted_email.html.haml5
-rw-r--r--app/views/notify/group_access_granted_email.text.erb4
-rw-r--r--app/views/notify/issue_status_changed_email.html.haml21
-rw-r--r--app/views/notify/issue_status_changed_email.text.erb4
-rw-r--r--app/views/notify/merged_merge_request_email.html.haml9
-rw-r--r--app/views/notify/merged_merge_request_email.text.haml8
-rw-r--r--app/views/notify/new_issue_email.html.haml24
-rw-r--r--app/views/notify/new_issue_email.text.erb5
-rw-r--r--app/views/notify/new_merge_request_email.html.haml26
-rw-r--r--app/views/notify/new_merge_request_email.text.erb8
-rw-r--r--app/views/notify/new_ssh_key_email.html.haml10
-rw-r--r--app/views/notify/new_ssh_key_email.text.erb7
-rw-r--r--app/views/notify/new_user_email.html.haml46
-rw-r--r--app/views/notify/new_user_email.text.erb13
-rw-r--r--app/views/notify/note_commit_email.html.haml26
-rw-r--r--app/views/notify/note_commit_email.text.erb9
-rw-r--r--app/views/notify/note_issue_email.html.haml27
-rw-r--r--app/views/notify/note_issue_email.text.erb9
-rw-r--r--app/views/notify/note_merge_request_email.html.haml35
-rw-r--r--app/views/notify/note_merge_request_email.text.erb9
-rw-r--r--app/views/notify/note_wall_email.html.haml27
-rw-r--r--app/views/notify/note_wall_email.text.erb9
-rw-r--r--app/views/notify/project_access_granted_email.html.haml20
-rw-r--r--app/views/notify/project_access_granted_email.text.erb4
-rw-r--r--app/views/notify/project_was_moved_email.html.haml36
-rw-r--r--app/views/notify/project_was_moved_email.text.erb8
-rw-r--r--app/views/notify/reassigned_issue_email.html.haml25
-rw-r--r--app/views/notify/reassigned_issue_email.text.erb5
-rw-r--r--app/views/notify/reassigned_merge_request_email.html.haml26
-rw-r--r--app/views/notify/reassigned_merge_request_email.text.erb7
-rw-r--r--app/views/profiles/account.html.haml209
-rw-r--r--app/views/profiles/design.html.haml30
-rw-r--r--app/views/profiles/groups/index.html.haml38
-rw-r--r--app/views/profiles/history.html.haml5
-rw-r--r--app/views/profiles/keys/_form.html.haml23
-rw-r--r--app/views/profiles/keys/_key.html.haml11
-rw-r--r--app/views/profiles/keys/index.html.haml22
-rw-r--r--app/views/profiles/keys/new.html.haml (renamed from app/views/keys/new.html.haml)2
-rw-r--r--app/views/profiles/keys/show.html.haml22
-rw-r--r--app/views/profiles/notifications/_settings.html.haml31
-rw-r--r--app/views/profiles/notifications/show.html.haml58
-rw-r--r--app/views/profiles/notifications/update.js.haml6
-rw-r--r--app/views/profiles/passwords/new.html.haml22
-rw-r--r--app/views/profiles/show.html.haml40
-rw-r--r--app/views/projects/_clone_panel.html.haml59
-rw-r--r--app/views/projects/_errors.html.haml4
-rw-r--r--app/views/projects/_form.html.haml85
-rw-r--r--app/views/projects/_new_form.html.haml48
-rw-r--r--app/views/projects/_project_head.html.haml31
-rw-r--r--app/views/projects/_settings_nav.html.haml21
-rw-r--r--app/views/projects/blame/show.html.haml (renamed from app/views/blame/show.html.haml)35
-rw-r--r--app/views/projects/blob/_actions.html.haml12
-rw-r--r--app/views/projects/blob/_blob.html.haml32
-rw-r--r--app/views/projects/blob/_download.html.haml (renamed from app/views/tree/blob/_download.html.haml)4
-rw-r--r--app/views/projects/blob/_image.html.haml (renamed from app/views/tree/blob/_image.html.haml)2
-rw-r--r--app/views/projects/blob/_text.html.haml (renamed from app/views/tree/blob/_text.html.haml)6
-rw-r--r--app/views/projects/blob/show.html.haml4
-rw-r--r--app/views/projects/branches/_branch.html.haml32
-rw-r--r--app/views/projects/branches/_filter.html.haml17
-rw-r--r--app/views/projects/branches/index.html.haml10
-rw-r--r--app/views/projects/branches/new.html.haml24
-rw-r--r--app/views/projects/branches/recent.html.haml8
-rw-r--r--app/views/projects/commit/_commit_box.html.haml47
-rw-r--r--app/views/projects/commit/huge_commit.html.haml (renamed from app/views/commit/huge_commit.html.haml)2
-rw-r--r--app/views/projects/commit/show.html.haml3
-rw-r--r--app/views/projects/commits/_commit.html.haml19
-rw-r--r--app/views/projects/commits/_commits.html.haml11
-rw-r--r--app/views/projects/commits/_diff_head.html.haml (renamed from app/views/commits/_diff_head.html.haml)0
-rw-r--r--app/views/projects/commits/_diffs.html.haml70
-rw-r--r--app/views/projects/commits/_head.html.haml (renamed from app/views/commits/_head.html.haml)15
-rw-r--r--app/views/projects/commits/_image.html.haml (renamed from app/views/commits/_image.html.haml)4
-rw-r--r--app/views/projects/commits/_inline_commit.html.haml9
-rw-r--r--app/views/projects/commits/_text_file.html.haml (renamed from app/views/commits/_text_file.html.haml)8
-rw-r--r--app/views/projects/commits/show.atom.builder (renamed from app/views/commits/show.atom.builder)0
-rw-r--r--app/views/projects/commits/show.html.haml (renamed from app/views/commits/show.html.haml)10
-rw-r--r--app/views/projects/commits/show.js.haml (renamed from app/views/commits/show.js.haml)2
-rw-r--r--app/views/projects/compare/_form.html.haml29
-rw-r--r--app/views/projects/compare/index.html.haml16
-rw-r--r--app/views/projects/compare/show.html.haml35
-rw-r--r--app/views/projects/create.js.haml7
-rw-r--r--app/views/projects/deploy_keys/_deploy_key.html.haml25
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml (renamed from app/views/deploy_keys/_form.html.haml)18
-rw-r--r--app/views/projects/deploy_keys/index.html.haml32
-rw-r--r--app/views/projects/deploy_keys/new.html.haml4
-rw-r--r--app/views/projects/deploy_keys/show.html.haml (renamed from app/views/deploy_keys/show.html.haml)7
-rw-r--r--app/views/projects/edit.html.haml179
-rw-r--r--app/views/projects/edit_tree/show.html.haml46
-rw-r--r--app/views/projects/empty.html.haml68
-rw-r--r--app/views/projects/files.html.haml22
-rw-r--r--app/views/projects/fork.html.haml19
-rw-r--r--app/views/projects/graphs/show.html.haml34
-rw-r--r--app/views/projects/graphs/show.js.haml19
-rw-r--r--app/views/projects/hooks/_data_ex.html.erb (renamed from app/views/hooks/_data_ex.html.erb)0
-rw-r--r--app/views/projects/hooks/index.html.haml (renamed from app/views/hooks/index.html.haml)41
-rw-r--r--app/views/projects/issues/_form.html.haml95
-rw-r--r--app/views/projects/issues/_head.html.haml (renamed from app/views/issues/_head.html.haml)6
-rw-r--r--app/views/projects/issues/_issue.html.haml (renamed from app/views/issues/_show.html.haml)59
-rw-r--r--app/views/projects/issues/_issues.html.haml93
-rw-r--r--app/views/projects/issues/edit.html.haml (renamed from app/views/issues/edit.html.haml)0
-rw-r--r--app/views/projects/issues/index.atom.builder (renamed from app/views/issues/index.atom.builder)0
-rw-r--r--app/views/projects/issues/index.html.haml23
-rw-r--r--app/views/projects/issues/index.js.haml4
-rw-r--r--app/views/projects/issues/new.html.haml (renamed from app/views/issues/new.html.haml)0
-rw-r--r--app/views/projects/issues/show.html.haml74
-rw-r--r--app/views/projects/issues/update.js.haml (renamed from app/views/issues/update.js.haml)0
-rw-r--r--app/views/projects/labels/_label.html.haml15
-rw-r--r--app/views/projects/labels/index.html.haml10
-rw-r--r--app/views/projects/merge_requests/_form.html.haml87
-rw-r--r--app/views/projects/merge_requests/_head.html.haml (renamed from app/views/merge_requests/_head.html.haml)0
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml37
-rw-r--r--app/views/projects/merge_requests/_show.html.haml38
-rw-r--r--app/views/projects/merge_requests/automerge.js.haml (renamed from app/views/merge_requests/automerge.js.haml)0
-rw-r--r--app/views/projects/merge_requests/branch_from.js.haml7
-rw-r--r--app/views/projects/merge_requests/branch_to.js.haml2
-rw-r--r--app/views/projects/merge_requests/commits.js.haml (renamed from app/views/merge_requests/commits.js.haml)0
-rw-r--r--app/views/projects/merge_requests/diffs.html.haml (renamed from app/views/merge_requests/diffs.html.haml)0
-rw-r--r--app/views/projects/merge_requests/diffs.js.haml (renamed from app/views/merge_requests/diffs.js.haml)2
-rw-r--r--app/views/projects/merge_requests/edit.html.haml4
-rw-r--r--app/views/projects/merge_requests/index.html.haml76
-rw-r--r--app/views/projects/merge_requests/invalid.html.haml17
-rw-r--r--app/views/projects/merge_requests/new.html.haml3
-rw-r--r--app/views/projects/merge_requests/show.html.haml (renamed from app/views/merge_requests/show.html.haml)0
-rw-r--r--app/views/projects/merge_requests/show.js.haml (renamed from app/views/merge_requests/show.js.haml)0
-rw-r--r--app/views/projects/merge_requests/show/_commits.html.haml (renamed from app/views/merge_requests/show/_commits.html.haml)14
-rw-r--r--app/views/projects/merge_requests/show/_diffs.html.haml10
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml51
-rw-r--r--app/views/projects/merge_requests/show/_mr_accept.html.haml (renamed from app/views/merge_requests/show/_mr_accept.html.haml)23
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml48
-rw-r--r--app/views/projects/merge_requests/show/_mr_ci.html.haml (renamed from app/views/merge_requests/show/_mr_ci.html.haml)2
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml (renamed from app/views/merge_requests/show/_mr_title.html.haml)25
-rw-r--r--app/views/projects/merge_requests/update_branches.js.haml5
-rw-r--r--app/views/projects/milestones/_form.html.haml (renamed from app/views/milestones/_form.html.haml)20
-rw-r--r--app/views/projects/milestones/_issues.html.haml11
-rw-r--r--app/views/projects/milestones/_merge_request.html.haml5
-rw-r--r--app/views/projects/milestones/_milestone.html.haml (renamed from app/views/milestones/_milestone.html.haml)16
-rw-r--r--app/views/projects/milestones/edit.html.haml (renamed from app/views/milestones/edit.html.haml)0
-rw-r--r--app/views/projects/milestones/index.html.haml (renamed from app/views/milestones/index.html.haml)31
-rw-r--r--app/views/projects/milestones/new.html.haml (renamed from app/views/milestones/new.html.haml)0
-rw-r--r--app/views/projects/milestones/show.html.haml105
-rw-r--r--app/views/projects/milestones/update.js.haml2
-rw-r--r--app/views/projects/network/_head.html.haml23
-rw-r--r--app/views/projects/network/show.html.haml14
-rw-r--r--app/views/projects/network/show.json.erb23
-rw-r--r--app/views/projects/new.html.haml79
-rw-r--r--app/views/projects/notes/_diff_note_link.html.haml (renamed from app/views/notes/_diff_note_link.html.haml)0
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply.html.haml13
-rw-r--r--app/views/projects/notes/_discussion.html.haml (renamed from app/views/notes/_discussion.html.haml)10
-rw-r--r--app/views/projects/notes/_discussion_diff.html.haml (renamed from app/views/notes/_discussion_diff.html.haml)5
-rw-r--r--app/views/projects/notes/_discussion_reply_button.html.haml (renamed from app/views/notes/_discussion_reply_button.html.haml)0
-rw-r--r--app/views/projects/notes/_form.html.haml (renamed from app/views/notes/_form.html.haml)13
-rw-r--r--app/views/projects/notes/_form_errors.html.haml (renamed from app/views/notes/_form_errors.html.haml)2
-rw-r--r--app/views/projects/notes/_note.html.haml66
-rw-r--r--app/views/projects/notes/_notes.html.haml (renamed from app/views/notes/_notes.html.haml)2
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml9
-rw-r--r--app/views/projects/notes/create.js.haml (renamed from app/views/notes/create.js.haml)13
-rw-r--r--app/views/projects/notes/index.js.haml4
-rw-r--r--app/views/projects/protected_branches/index.html.haml53
-rw-r--r--app/views/projects/refs/logs_tree.js.haml (renamed from app/views/refs/logs_tree.js.haml)5
-rw-r--r--app/views/projects/repositories/_feed.html.haml (renamed from app/views/repositories/_feed.html.haml)3
-rw-r--r--app/views/projects/repositories/stats.html.haml (renamed from app/views/repositories/stats.html.haml)30
-rw-r--r--app/views/projects/services/_form.html.haml48
-rw-r--r--app/views/projects/services/edit.html.haml1
-rw-r--r--app/views/projects/services/index.html.haml17
-rw-r--r--app/views/projects/show.html.haml51
-rw-r--r--app/views/projects/snippets/_blob.html.haml15
-rw-r--r--app/views/projects/snippets/_form.html.haml44
-rw-r--r--app/views/projects/snippets/_snippet.html.haml21
-rw-r--r--app/views/projects/snippets/edit.html.haml1
-rw-r--r--app/views/projects/snippets/index.html.haml15
-rw-r--r--app/views/projects/snippets/new.html.haml1
-rw-r--r--app/views/projects/snippets/show.html.haml11
-rw-r--r--app/views/projects/tags/index.html.haml52
-rw-r--r--app/views/projects/tags/new.html.haml24
-rw-r--r--app/views/projects/team_members/_form.html.haml24
-rw-r--r--app/views/projects/team_members/_group_members.html.haml10
-rw-r--r--app/views/projects/team_members/_team.html.haml9
-rw-r--r--app/views/projects/team_members/_team_member.html.haml17
-rw-r--r--app/views/projects/team_members/import.html.haml14
-rw-r--r--app/views/projects/team_members/index.html.haml16
-rw-r--r--app/views/projects/team_members/new.html.haml1
-rw-r--r--app/views/projects/team_members/update.js.haml (renamed from app/views/team_members/update.js.haml)0
-rw-r--r--app/views/projects/teams/available.html.haml22
-rw-r--r--app/views/projects/transfer.js.haml7
-rw-r--r--app/views/projects/tree.js.haml5
-rw-r--r--app/views/projects/tree/_blob_item.html.haml9
-rw-r--r--app/views/projects/tree/_readme.html.haml (renamed from app/views/tree/_readme.html.haml)6
-rw-r--r--app/views/projects/tree/_submodule_item.html.haml (renamed from app/views/tree/_submodule_item.html.haml)2
-rw-r--r--app/views/projects/tree/_tree.html.haml51
-rw-r--r--app/views/projects/tree/_tree_commit_column.html.haml (renamed from app/views/tree/_tree_commit_column.html.haml)2
-rw-r--r--app/views/projects/tree/_tree_item.html.haml (renamed from app/views/tree/_tree_item.html.haml)2
-rw-r--r--app/views/projects/tree/show.html.haml4
-rw-r--r--app/views/projects/update.js.haml5
-rw-r--r--app/views/projects/update_failed.js.haml2
-rw-r--r--app/views/projects/wall.html.haml2
-rw-r--r--app/views/projects/walls/show.html.haml23
-rw-r--r--app/views/projects/wikis/_form.html.haml39
-rw-r--r--app/views/projects/wikis/_main_links.html.haml8
-rw-r--r--app/views/projects/wikis/_nav.html.haml19
-rw-r--r--app/views/projects/wikis/_new.html.haml12
-rw-r--r--app/views/projects/wikis/edit.html.haml (renamed from app/views/wikis/edit.html.haml)10
-rw-r--r--app/views/projects/wikis/empty.html.haml (renamed from app/views/wikis/empty.html.haml)2
-rw-r--r--app/views/projects/wikis/git_access.html.haml36
-rw-r--r--app/views/projects/wikis/history.html.haml30
-rw-r--r--app/views/projects/wikis/pages.html.haml11
-rw-r--r--app/views/projects/wikis/show.html.haml15
-rw-r--r--app/views/protected_branches/index.html.haml54
-rw-r--r--app/views/public/projects/index.html.haml50
-rw-r--r--app/views/repositories/_branch.html.haml27
-rw-r--r--app/views/repositories/_filter.html.haml9
-rw-r--r--app/views/repositories/_head.html.haml1
-rw-r--r--app/views/repositories/branches.html.haml15
-rw-r--r--app/views/repositories/show.html.haml14
-rw-r--r--app/views/repositories/tags.html.haml39
-rw-r--r--app/views/search/_blob.html.haml10
-rw-r--r--app/views/search/_filter.html.haml45
-rw-r--r--app/views/search/_result.html.haml40
-rw-r--r--app/views/search/show.html.haml21
-rw-r--r--app/views/services/_gitlab_ci.html.haml46
-rw-r--r--app/views/services/edit.html.haml2
-rw-r--r--app/views/services/index.html.haml31
-rw-r--r--app/views/shared/_clone_panel.html.haml14
-rw-r--r--app/views/shared/_event_filter.html.haml5
-rw-r--r--app/views/shared/_filter.html.haml29
-rw-r--r--app/views/shared/_issues.html.haml15
-rw-r--r--app/views/shared/_merge_requests.html.haml13
-rw-r--r--app/views/shared/_no_ssh.html.haml6
-rw-r--r--app/views/shared/_project_filter.html.haml32
-rw-r--r--app/views/shared/_promo.html.haml4
-rw-r--r--app/views/shared/_ref_switcher.html.haml2
-rw-r--r--app/views/snippets/_blob.html.haml12
-rw-r--r--app/views/snippets/_form.html.haml35
-rw-r--r--app/views/snippets/_snippet.html.haml36
-rw-r--r--app/views/snippets/_snippets.html.haml7
-rw-r--r--app/views/snippets/current_user_index.html.haml34
-rw-r--r--app/views/snippets/edit.html.haml3
-rw-r--r--app/views/snippets/index.html.haml32
-rw-r--r--app/views/snippets/new.html.haml3
-rw-r--r--app/views/snippets/show.html.haml36
-rw-r--r--app/views/snippets/user_index.html.haml12
-rw-r--r--app/views/team_members/_form.html.haml23
-rw-r--r--app/views/team_members/_show.html.haml28
-rw-r--r--app/views/team_members/_show_team.html.haml15
-rw-r--r--app/views/team_members/_team.html.haml16
-rw-r--r--app/views/team_members/_teams.html.haml16
-rw-r--r--app/views/team_members/create.js.haml13
-rw-r--r--app/views/team_members/import.html.haml17
-rw-r--r--app/views/team_members/index.html.haml33
-rw-r--r--app/views/team_members/new.html.haml2
-rw-r--r--app/views/team_members/show.html.haml59
-rw-r--r--app/views/teams/_filter.html.haml33
-rw-r--r--app/views/teams/_projects.html.haml22
-rw-r--r--app/views/teams/edit.html.haml20
-rw-r--r--app/views/teams/issues.html.haml23
-rw-r--r--app/views/teams/members/_form.html.haml20
-rw-r--r--app/views/teams/members/_show.html.haml31
-rw-r--r--app/views/teams/members/_team.html.haml16
-rw-r--r--app/views/teams/members/edit.html.haml16
-rw-r--r--app/views/teams/members/index.html.haml17
-rw-r--r--app/views/teams/members/new.html.haml28
-rw-r--r--app/views/teams/members/show.html.haml60
-rw-r--r--app/views/teams/merge_requests.html.haml24
-rw-r--r--app/views/teams/new.html.haml19
-rw-r--r--app/views/teams/projects/_form.html.haml16
-rw-r--r--app/views/teams/projects/edit.html.haml6
-rw-r--r--app/views/teams/projects/index.html.haml36
-rw-r--r--app/views/teams/projects/new.html.haml23
-rw-r--r--app/views/teams/show.html.haml28
-rw-r--r--app/views/teams/show.js.haml2
-rw-r--r--app/views/tree/_blob.html.haml13
-rw-r--r--app/views/tree/_blob_actions.html.haml12
-rw-r--r--app/views/tree/_head.html.haml7
-rw-r--r--app/views/tree/_tree.html.haml48
-rw-r--r--app/views/tree/edit.html.haml44
-rw-r--r--app/views/tree/show.html.haml3
-rw-r--r--app/views/tree/show.js.haml10
-rw-r--r--app/views/users/_profile.html.haml22
-rw-r--r--app/views/users/_projects.html.haml11
-rw-r--r--app/views/users/show.html.haml10
-rw-r--r--app/views/users_groups/_users_group.html.haml23
-rw-r--r--app/views/users_groups/update.js.haml2
-rw-r--r--app/views/votes/_votes_inline.html.haml13
-rw-r--r--app/views/wikis/_form.html.haml27
-rw-r--r--app/views/wikis/history.html.haml23
-rw-r--r--app/views/wikis/pages.html.haml20
-rw-r--r--app/views/wikis/show.html.haml23
-rw-r--r--app/workers/gitlab_shell_worker.rb2
-rw-r--r--app/workers/post_receive.rb29
-rw-r--r--app/workers/repository_import_worker.rb22
-rw-r--r--config/application.rb13
-rw-r--r--config/aws.yml.example19
-rw-r--r--config/database.yml.mysql2
-rw-r--r--config/database.yml.postgresql4
-rw-r--r--config/environments/production.rb15
-rw-r--r--config/gitlab.yml.example113
-rw-r--r--config/initializers/1_settings.rb42
-rw-r--r--config/initializers/2_app.rb9
-rw-r--r--config/initializers/3_grit_ext.rb5
-rw-r--r--config/initializers/4_sidekiq.rb6
-rw-r--r--config/initializers/5_backend.rb6
-rw-r--r--config/initializers/carrierwave.rb18
-rw-r--r--config/initializers/connection_fix.rb36
-rw-r--r--config/initializers/devise.rb29
-rw-r--r--config/initializers/haml.rb1
-rw-r--r--config/initializers/secret_token.rb18
-rw-r--r--config/initializers/session_store.rb3
-rw-r--r--config/initializers/smtp_settings.rb.sample18
-rw-r--r--config/locales/devise.en.yml1
-rw-r--r--config/resque.yml.example6
-rw-r--r--config/routes.rb283
-rw-r--r--config/unicorn.rb.example114
-rw-r--r--db/fixtures/development/01_admin.rb2
-rw-r--r--db/fixtures/development/02_source_code.rb29
-rw-r--r--db/fixtures/development/03_group.rb5
-rw-r--r--db/fixtures/development/04_project.rb61
-rw-r--r--db/fixtures/development/05_users.rb2
-rw-r--r--db/fixtures/development/06_teams.rb37
-rw-r--r--db/fixtures/development/07_milestones.rb25
-rw-r--r--db/fixtures/development/09_issues.rb11
-rw-r--r--db/fixtures/development/10_merge_requests.rb32
-rw-r--r--db/fixtures/development/11_keys.rb1
-rw-r--r--db/fixtures/development/12_snippets.rb24
-rw-r--r--db/fixtures/production/001_admin.rb5
-rw-r--r--db/fixtures/test/001_repo.rb13
-rw-r--r--db/migrate/20130123114545_add_issues_tracker_to_project.rb5
-rw-r--r--db/migrate/20130206084024_add_description_to_namsespace.rb5
-rw-r--r--db/migrate/20130207104426_add_description_to_teams.rb5
-rw-r--r--db/migrate/20130211085435_add_issues_tracker_id_to_project.rb5
-rw-r--r--db/migrate/20130214154045_rename_state_to_merge_status_in_milestone.rb5
-rw-r--r--db/migrate/20130218140952_add_state_to_issue.rb5
-rw-r--r--db/migrate/20130218141038_add_state_to_merge_request.rb5
-rw-r--r--db/migrate/20130218141117_add_state_to_milestone.rb5
-rw-r--r--db/migrate/20130218141258_convert_closed_to_state_in_issue.rb14
-rw-r--r--db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb16
-rw-r--r--db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb14
-rw-r--r--db/migrate/20130218141444_remove_merged_from_merge_request.rb9
-rw-r--r--db/migrate/20130218141507_remove_closed_from_issue.rb9
-rw-r--r--db/migrate/20130218141536_remove_closed_from_merge_request.rb9
-rw-r--r--db/migrate/20130218141554_remove_closed_from_milestone.rb9
-rw-r--r--db/migrate/20130220124204_add_new_merge_status_to_merge_request.rb5
-rw-r--r--db/migrate/20130220125544_convert_merge_status_in_merge_request.rb17
-rw-r--r--db/migrate/20130220125545_remove_merge_status_from_merge_request.rb9
-rw-r--r--db/migrate/20130220133245_rename_new_merge_status_to_merge_status_in_milestone.rb5
-rw-r--r--db/migrate/20130304104623_add_state_to_user.rb5
-rw-r--r--db/migrate/20130304104740_convert_blocked_to_state.rb14
-rw-r--r--db/migrate/20130304105317_remove_blocked_from_user.rb9
-rw-r--r--db/migrate/20130315124931_user_color_scheme.rb12
-rw-r--r--db/migrate/20130318212250_add_snippets_to_features.rb5
-rw-r--r--db/migrate/20130319214458_create_forked_project_links.rb11
-rw-r--r--db/migrate/20130323174317_add_private_to_snippets.rb5
-rw-r--r--db/migrate/20130324151736_add_type_to_snippets.rb5
-rw-r--r--db/migrate/20130324172327_change_project_id_to_null_in_snipepts.rb9
-rw-r--r--db/migrate/20130324203535_add_type_value_for_snippets.rb8
-rw-r--r--db/migrate/20130325173941_add_notification_level_to_user.rb5
-rw-r--r--db/migrate/20130326142630_add_index_to_users_authentication_token.rb5
-rw-r--r--db/migrate/20130403003950_add_last_activity_column_into_project.rb21
-rw-r--r--db/migrate/20130404164628_add_notification_level_to_user_project.rb5
-rw-r--r--db/migrate/20130410175022_remove_wiki_table.rb9
-rw-r--r--db/migrate/20130419190306_allow_merges_for_forks.rb13
-rw-r--r--db/migrate/20130506085413_add_type_to_key.rb5
-rw-r--r--db/migrate/20130506090604_create_deploy_keys_projects.rb10
-rw-r--r--db/migrate/20130506095501_remove_project_id_from_key.rb22
-rw-r--r--db/migrate/20130522141856_add_more_fields_to_service.rb6
-rw-r--r--db/migrate/20130528184641_add_system_to_notes.rb16
-rw-r--r--db/migrate/20130611210815_increase_snippet_text_column_size.rb9
-rw-r--r--db/migrate/20130613165816_add_password_expires_at_to_users.rb5
-rw-r--r--db/migrate/20130613173246_add_created_by_id_to_user.rb5
-rw-r--r--db/migrate/20130614132337_add_improted_to_project.rb5
-rw-r--r--db/migrate/20130617095603_create_users_groups.rb11
-rw-r--r--db/migrate/20130621195223_add_notification_level_to_user_group.rb5
-rw-r--r--db/migrate/20130622115340_add_more_db_index.rb12
-rw-r--r--db/migrate/20130624162710_add_fingerprint_to_key.rb6
-rw-r--r--db/migrate/20130804151314_add_st_diff_to_note.rb5
-rw-r--r--db/migrate/20130812143708_add_import_url_to_project.rb5
-rw-r--r--db/migrate/20130819182730_add_internal_ids_to_issues_and_mr.rb6
-rw-r--r--db/migrate/20130821090530_remove_deprecated_tables.rb11
-rw-r--r--db/migrate/20130821090531_add_internal_ids_to_milestones.rb5
-rw-r--r--db/migrate/20130909132950_add_description_to_merge_request.rb5
-rw-r--r--db/migrate/20130926081215_change_owner_id_for_group.rb9
-rw-r--r--db/schema.rb200
-rw-r--r--doc/api/README.md107
-rw-r--r--doc/api/deploy_keys.md87
-rw-r--r--doc/api/groups.md84
-rw-r--r--doc/api/issues.md44
-rw-r--r--doc/api/merge_requests.md20
-rw-r--r--doc/api/milestones.md19
-rw-r--r--doc/api/notes.md115
-rw-r--r--doc/api/project_snippets.md (renamed from doc/api/snippets.md)41
-rw-r--r--doc/api/projects.md341
-rw-r--r--doc/api/repositories.md134
-rw-r--r--doc/api/session.md17
-rw-r--r--doc/api/system_hooks.md49
-rw-r--r--doc/api/user_teams.md209
-rw-r--r--doc/api/users.md118
-rw-r--r--doc/install/databases.md36
-rw-r--r--doc/install/installation.md245
-rw-r--r--doc/install/requirements.md38
-rw-r--r--doc/make_release.md56
-rw-r--r--doc/markdown/markdown.md472
-rw-r--r--doc/raketasks/backup_restore.md2
-rw-r--r--doc/raketasks/cleanup.md12
-rw-r--r--doc/raketasks/features.md6
-rw-r--r--doc/raketasks/maintenance.md121
-rw-r--r--doc/raketasks/user_management.md2
-rw-r--r--doc/update/2.6-to-3.0.md63
-rw-r--r--doc/update/2.9-to-3.0.md37
-rw-r--r--doc/update/3.0-to-3.1.md108
-rw-r--r--doc/update/3.1-to-4.0.md99
-rw-r--r--doc/update/4.0-to-4.1.md57
-rw-r--r--doc/update/4.1-to-4.2.md38
-rw-r--r--doc/update/4.2-to-5.0.md164
-rw-r--r--doc/update/5.0-to-5.1.md68
-rw-r--r--doc/update/5.1-to-5.2.md99
-rw-r--r--doc/update/5.2-to-5.3.md82
-rw-r--r--doc/update/5.3-to-5.4.md90
-rw-r--r--doc/update/5.4-to-6.0.md113
-rw-r--r--doc/update/6.0-to-6.1.md102
-rw-r--r--features/admin/groups.feature4
-rw-r--r--features/admin/teams.feature70
-rw-r--r--features/admin/users.feature8
-rw-r--r--features/dashboard/active_tab.feature5
-rw-r--r--features/dashboard/dashboard.feature1
-rw-r--r--features/dashboard/projects.feature4
-rw-r--r--features/dashboard/search.feature5
-rw-r--r--features/group/group.feature3
-rw-r--r--features/profile/notifications.feature8
-rw-r--r--features/profile/profile.feature19
-rw-r--r--features/project/active_tab.feature61
-rw-r--r--features/project/commits/commit_diff_comments.feature7
-rw-r--r--features/project/commits/commits.feature12
-rw-r--r--features/project/create_project.feature11
-rw-r--r--features/project/deploy_keys.feature23
-rw-r--r--features/project/fork_project.feature14
-rw-r--r--features/project/forked_merge_requests.feature34
-rw-r--r--features/project/graph.feature9
-rw-r--r--features/project/issues/issues.feature26
-rw-r--r--features/project/issues/milestones.feature2
-rw-r--r--features/project/merge_requests.feature1
-rw-r--r--features/project/network.feature31
-rw-r--r--features/project/project.feature4
-rw-r--r--features/project/public_projects.feature8
-rw-r--r--features/project/service.feature12
-rw-r--r--features/project/snippets.feature35
-rw-r--r--features/project/source/browse_files.feature4
-rw-r--r--features/project/source/git_blame.feature2
-rw-r--r--features/project/source/search_code.feature9
-rw-r--r--features/project/team_management.feature12
-rw-r--r--features/project/wiki.feature42
-rw-r--r--features/public/public_projects.feature18
-rw-r--r--features/snippets/discover_snippets.feature10
-rw-r--r--features/snippets/snippets.feature28
-rw-r--r--features/snippets/user_snippets.feature22
-rw-r--r--features/steps/admin/admin_groups.rb26
-rw-r--r--features/steps/admin/admin_projects.rb2
-rw-r--r--features/steps/admin/admin_teams.rb234
-rw-r--r--features/steps/admin/admin_users.rb23
-rw-r--r--features/steps/dashboard/dashboard.rb16
-rw-r--r--features/steps/dashboard/dashboard_active_tab.rb4
-rw-r--r--features/steps/dashboard/dashboard_event_filters.rb12
-rw-r--r--features/steps/dashboard/dashboard_merge_requests.rb20
-rw-r--r--features/steps/dashboard/dashboard_projects.rb11
-rw-r--r--features/steps/dashboard/dashboard_search.rb11
-rw-r--r--features/steps/group/group.rb27
-rw-r--r--features/steps/profile/profile.rb123
-rw-r--r--features/steps/profile/profile_notifications.rb13
-rw-r--r--features/steps/profile/profile_ssh_keys.rb14
-rw-r--r--features/steps/project/create_project.rb22
-rw-r--r--features/steps/project/deploy_keys.rb53
-rw-r--r--features/steps/project/project.rb2
-rw-r--r--features/steps/project/project_active_tab.rb36
-rw-r--r--features/steps/project/project_browse_branches.rb4
-rw-r--r--features/steps/project/project_browse_commits.rb42
-rw-r--r--features/steps/project/project_browse_files.rb6
-rw-r--r--features/steps/project/project_browse_git_repo.rb6
-rw-r--r--features/steps/project/project_fork.rb36
-rw-r--r--features/steps/project/project_forked_merge_requests.rb183
-rw-r--r--features/steps/project/project_graph.rb13
-rw-r--r--features/steps/project/project_issues.rb49
-rw-r--r--features/steps/project/project_merge_requests.rb74
-rw-r--r--features/steps/project/project_milestones.rb14
-rw-r--r--features/steps/project/project_network_graph.rb89
-rw-r--r--features/steps/project/project_search_code.rb17
-rw-r--r--features/steps/project/project_services.rb39
-rw-r--r--features/steps/project/project_snippets.rb100
-rw-r--r--features/steps/project/project_team_management.rb64
-rw-r--r--features/steps/project/project_wall.rb12
-rw-r--r--features/steps/project/project_wiki.rb84
-rw-r--r--features/steps/project/public_projects.rb9
-rw-r--r--features/steps/public/projects_feature.rb61
-rw-r--r--features/steps/shared/active_tab.rb14
-rw-r--r--features/steps/shared/diff_note.rb26
-rw-r--r--features/steps/shared/note.rb19
-rw-r--r--features/steps/shared/paths.rb206
-rw-r--r--features/steps/shared/project.rb11
-rw-r--r--features/steps/shared/snippet.rb21
-rw-r--r--features/steps/snippets/discover_snippets.rb17
-rw-r--r--features/steps/snippets/snippets.rb64
-rw-r--r--features/steps/snippets/user_snippets.rb41
-rw-r--r--features/steps/userteams/userteams.rb254
-rw-r--r--features/support/env.rb21
-rw-r--r--features/teams/team.feature69
-rw-r--r--lib/api.rb25
-rw-r--r--lib/api/api.rb42
-rw-r--r--lib/api/deploy_keys.rb84
-rw-r--r--lib/api/entities.rb66
-rw-r--r--lib/api/groups.rb92
-rw-r--r--lib/api/helpers.rb54
-rw-r--r--lib/api/internal.rb40
-rw-r--r--lib/api/issues.rb10
-rw-r--r--lib/api/merge_requests.rb49
-rw-r--r--lib/api/milestones.rb7
-rw-r--r--lib/api/notes.rb12
-rw-r--r--lib/api/project_hooks.rb108
-rw-r--r--lib/api/project_snippets.rb123
-rw-r--r--lib/api/projects.rb463
-rw-r--r--lib/api/repositories.rb190
-rw-r--r--lib/api/session.rb19
-rw-r--r--lib/api/system_hooks.rb70
-rw-r--r--lib/api/users.rb51
-rw-r--r--lib/backup/database.rb58
-rw-r--r--lib/backup/manager.rb106
-rw-r--r--lib/backup/repository.rb105
-rw-r--r--lib/backup/uploads.rb29
-rw-r--r--lib/extracts_path.rb66
-rw-r--r--lib/gitlab/access.rb52
-rw-r--r--lib/gitlab/auth.rb70
-rw-r--r--lib/gitlab/backend/grack_auth.rb123
-rw-r--r--lib/gitlab/backend/grack_helpers.rb28
-rw-r--r--lib/gitlab/backend/shell.rb169
-rw-r--r--lib/gitlab/backend/shell_adapter.rb12
-rw-r--r--lib/gitlab/backend/shell_env.rb2
-rw-r--r--lib/gitlab/blacklist.rb9
-rw-r--r--lib/gitlab/diff_parser.rb77
-rw-r--r--lib/gitlab/git_stats.rb73
-rw-r--r--lib/gitlab/graph/commit.rb52
-rw-r--r--lib/gitlab/graph/json_builder.rb268
-rw-r--r--lib/gitlab/identifier.rb23
-rw-r--r--lib/gitlab/inline_diff.rb21
-rw-r--r--lib/gitlab/issues_labels.rb28
-rw-r--r--lib/gitlab/ldap/user.rb94
-rw-r--r--lib/gitlab/markdown.rb42
-rw-r--r--lib/gitlab/oauth/user.rb85
-rw-r--r--lib/gitlab/popen.rb2
-rw-r--r--lib/gitlab/project_mover.rb45
-rw-r--r--lib/gitlab/reference_extractor.rb59
-rw-r--r--lib/gitlab/regex.rb8
-rw-r--r--lib/gitlab/satellite/action.rb22
-rw-r--r--lib/gitlab/satellite/edit_file_action.rb4
-rw-r--r--lib/gitlab/satellite/merge_action.rb127
-rw-r--r--lib/gitlab/satellite/satellite.rb49
-rw-r--r--lib/gitlab/theme.rb16
-rw-r--r--lib/gitlab/user_team_manager.rb135
-rw-r--r--lib/gitlab/version_info.rb54
-rw-r--r--lib/gitolited.rb11
-rw-r--r--lib/redcarpet/render/gitlab_html.rb3
-rwxr-xr-xlib/support/deploy/deploy.sh44
-rwxr-xr-xlib/support/init.d/gitlab262
-rw-r--r--lib/support/nginx/gitlab39
-rw-r--r--lib/tasks/cache.rake6
-rw-r--r--lib/tasks/dev.rake10
-rw-r--r--lib/tasks/gitlab/backup.rake213
-rw-r--r--lib/tasks/gitlab/bulk_add_permission.rake8
-rw-r--r--lib/tasks/gitlab/check.rake194
-rw-r--r--lib/tasks/gitlab/cleanup.rake6
-rw-r--r--lib/tasks/gitlab/enable_namespaces.rake4
-rw-r--r--lib/tasks/gitlab/import.rake57
-rw-r--r--lib/tasks/gitlab/info.rake6
-rw-r--r--lib/tasks/gitlab/setup.rake16
-rw-r--r--lib/tasks/gitlab/shell.rake12
-rw-r--r--lib/tasks/gitlab/task_helpers.rake17
-rw-r--r--lib/tasks/gitlab/test.rake2
-rw-r--r--lib/tasks/migrate/migrate_iids.rake48
-rw-r--r--lib/tasks/sidekiq.rake10
-rw-r--r--lib/tasks/travis.rake2
-rw-r--r--public/deploy.html2
-rw-r--r--public/gitlab_logo.pngbin0 -> 17388 bytes
-rwxr-xr-xscript/check2
-rw-r--r--spec/contexts/filter_context_spec.rb59
-rw-r--r--spec/contexts/fork_context_spec.rb57
-rw-r--r--spec/contexts/issues/bulk_update_context_spec.rb113
-rw-r--r--spec/contexts/projects_create_context_spec.rb43
-rw-r--r--spec/controllers/application_controller_spec.rb33
-rw-r--r--spec/controllers/blob_controller_spec.rb37
-rw-r--r--spec/controllers/commit_controller_spec.rb21
-rw-r--r--spec/controllers/commits_controller_spec.rb8
-rw-r--r--spec/controllers/merge_requests_controller_spec.rb41
-rw-r--r--spec/controllers/tree_controller_spec.rb12
-rw-r--r--spec/factories.rb120
-rw-r--r--spec/factories/forked_project_links.rb19
-rw-r--r--spec/factories/user_team_project_relationships.rb21
-rw-r--r--spec/factories/user_team_user_relationships.rb23
-rw-r--r--spec/factories/user_teams.rb21
-rw-r--r--spec/factories/users_groups.rb20
-rw-r--r--spec/factories_spec.rb5
-rw-r--r--spec/features/admin/admin_hooks_spec.rb (renamed from spec/requests/admin/admin_hooks_spec.rb)2
-rw-r--r--spec/features/admin/admin_projects_spec.rb34
-rw-r--r--spec/features/admin/admin_users_spec.rb (renamed from spec/requests/admin/admin_users_spec.rb)52
-rw-r--r--spec/features/admin/security_spec.rb (renamed from spec/requests/admin/security_spec.rb)0
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb (renamed from spec/requests/atom/dashboard_issues_spec.rb)0
-rw-r--r--spec/features/atom/dashboard_spec.rb (renamed from spec/requests/atom/dashboard_spec.rb)0
-rw-r--r--spec/features/atom/issues_spec.rb (renamed from spec/requests/atom/issues_spec.rb)2
-rw-r--r--spec/features/gitlab_flavored_markdown_spec.rb129
-rw-r--r--spec/features/issues_spec.rb (renamed from spec/requests/issues_spec.rb)35
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb239
-rw-r--r--spec/features/profile_spec.rb (renamed from spec/requests/profile_spec.rb)23
-rw-r--r--spec/features/projects_spec.rb (renamed from spec/requests/projects_spec.rb)4
-rw-r--r--spec/features/search_spec.rb (renamed from spec/requests/search_spec.rb)10
-rw-r--r--spec/features/security/dashboard_access_spec.rb55
-rw-r--r--spec/features/security/group_access_spec.rb85
-rw-r--r--spec/features/security/profile_access_spec.rb (renamed from spec/requests/security/profile_access_spec.rb)31
-rw-r--r--spec/features/security/project/private_access_spec.rb218
-rw-r--r--spec/features/security/project/public_access_spec.rb251
-rw-r--r--spec/features/users_spec.rb19
-rw-r--r--spec/fixtures/dk.pngbin0 -> 1143 bytes
-rw-r--r--spec/helpers/application_helper_spec.rb22
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb121
-rw-r--r--spec/helpers/issues_helper_spec.rb106
-rw-r--r--spec/helpers/notifications_helper_spec.rb15
-rw-r--r--spec/javascripts/helpers/.gitkeep (renamed from app/views/admin/projects/team.html.haml)0
-rw-r--r--spec/javascripts/stat_graph_contributors_graph_spec.js125
-rw-r--r--spec/javascripts/stat_graph_contributors_util_spec.js200
-rw-r--r--spec/javascripts/stat_graph_spec.js17
-rw-r--r--spec/javascripts/support/jasmine.yml76
-rw-r--r--spec/javascripts/support/jasmine_helper.rb11
-rw-r--r--spec/lib/auth_spec.rb91
-rw-r--r--spec/lib/extracts_path_spec.rb42
-rw-r--r--spec/lib/gitlab/backend/shell_spec.rb (renamed from spec/lib/shell_spec.rb)1
-rw-r--r--spec/lib/gitlab/ldap/ldap_user_auth_spec.rb57
-rw-r--r--spec/lib/gitlab/popen_spec.rb29
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb95
-rw-r--r--spec/lib/gitlab/satellite/action_spec.rb116
-rw-r--r--spec/lib/gitlab/satellite/merge_action_spec.rb148
-rw-r--r--spec/lib/gitlab/version_info_spec.rb69
-rw-r--r--spec/lib/oauth_spec.rb44
-rw-r--r--spec/lib/project_mover_spec.rb66
-rw-r--r--spec/lib/votes_spec.rb116
-rw-r--r--spec/mailers/notify_spec.rb107
-rw-r--r--spec/models/commit_spec.rb111
-rw-r--r--spec/models/concerns/issuable_spec.rb3
-rw-r--r--spec/models/deploy_key_spec.rb25
-rw-r--r--spec/models/deploy_keys_project_spec.rb24
-rw-r--r--spec/models/event_spec.rb1
-rw-r--r--spec/models/forked_project_link_spec.rb67
-rw-r--r--spec/models/gitlab_ci_service_spec.rb2
-rw-r--r--spec/models/gollum_wiki_spec.rb196
-rw-r--r--spec/models/group_spec.rb32
-rw-r--r--spec/models/issue_spec.rb42
-rw-r--r--spec/models/key_spec.rb61
-rw-r--r--spec/models/merge_request_spec.rb112
-rw-r--r--spec/models/milestone_spec.rb42
-rw-r--r--spec/models/namespace_spec.rb19
-rw-r--r--spec/models/note_spec.rb123
-rw-r--r--spec/models/project_hooks_spec.rb128
-rw-r--r--spec/models/project_snippet_spec.rb32
-rw-r--r--spec/models/project_spec.rb136
-rw-r--r--spec/models/project_team_spec.rb3
-rw-r--r--spec/models/repository_spec.rb105
-rw-r--r--spec/models/service_spec.rb39
-rw-r--r--spec/models/snippet_spec.rb9
-rw-r--r--spec/models/user_spec.rb149
-rw-r--r--spec/models/user_team_project_relationship_spec.rb17
-rw-r--r--spec/models/user_team_spec.rb17
-rw-r--r--spec/models/user_team_user_relationship_spec.rb18
-rw-r--r--spec/models/users_group_spec.rb40
-rw-r--r--spec/models/users_project_spec.rb13
-rw-r--r--spec/models/wiki_page_spec.rb164
-rw-r--r--spec/models/wiki_spec.rb35
-rw-r--r--spec/observers/activity_observer_spec.rb37
-rw-r--r--spec/observers/issue_observer_spec.rb204
-rw-r--r--spec/observers/key_observer_spec.rb11
-rw-r--r--spec/observers/merge_request_observer_spec.rb199
-rw-r--r--spec/observers/note_observer_spec.rb118
-rw-r--r--spec/observers/user_observer_spec.rb6
-rw-r--r--spec/observers/users_group_observer_spec.rb27
-rw-r--r--spec/observers/users_project_observer_spec.rb10
-rw-r--r--spec/requests/admin/admin_projects_spec.rb76
-rw-r--r--spec/requests/api/api_helpers_spec.rb161
-rw-r--r--spec/requests/api/groups_spec.rb177
-rw-r--r--spec/requests/api/internal_spec.rb162
-rw-r--r--spec/requests/api/issues_spec.rb36
-rw-r--r--spec/requests/api/merge_requests_spec.rb159
-rw-r--r--spec/requests/api/milestones_spec.rb51
-rw-r--r--spec/requests/api/notes_spec.rb68
-rw-r--r--spec/requests/api/projects_spec.rb568
-rw-r--r--spec/requests/api/repositories_spec.rb217
-rw-r--r--spec/requests/api/session_spec.rb16
-rw-r--r--spec/requests/api/system_hooks_spec.rb81
-rw-r--r--spec/requests/api/users_spec.rb201
-rw-r--r--spec/requests/gitlab_flavored_markdown_spec.rb223
-rw-r--r--spec/requests/notes_on_merge_requests_spec.rb232
-rw-r--r--spec/requests/notes_on_wall_spec.rb85
-rw-r--r--spec/requests/projects_deploy_keys_spec.rb67
-rw-r--r--spec/requests/security/project_access_spec.rb243
-rw-r--r--spec/requests/snippets_spec.rb99
-rw-r--r--spec/routing/admin_routing_spec.rb61
-rw-r--r--spec/routing/notifications_routing_spec.rb13
-rw-r--r--spec/routing/project_routing_spec.rb294
-rw-r--r--spec/routing/routing_spec.rb71
-rw-r--r--spec/services/git_push_service_spec.rb215
-rw-r--r--spec/services/notification_service_spec.rb249
-rw-r--r--spec/services/project_transfer_service_spec.rb33
-rw-r--r--spec/services/system_hooks_service_spec.rb41
-rw-r--r--spec/spec_helper.rb101
-rw-r--r--spec/support/api_helpers.rb2
-rw-r--r--spec/support/big_commits.rb8
-rw-r--r--spec/support/chosen_helper.rb21
-rw-r--r--spec/support/db_cleaner.rb8
-rw-r--r--spec/support/login_helpers.rb8
-rw-r--r--spec/support/matchers.rb2
-rw-r--r--spec/support/mentionable_shared_examples.rb94
-rw-r--r--spec/support/select2_helper.rb25
-rw-r--r--spec/support/stubbed_repository.rb59
-rw-r--r--spec/support/test_env.rb173
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb12
-rw-r--r--spec/workers/post_receive_spec.rb4
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/ace.js12
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-coldfusion.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-groovy.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-html.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-jade.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-java.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-javascript.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-jsp.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-liquid.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-luahtml.js4
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-luapage.js4
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-markdown.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-php.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-scala.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/mode-svg.js2
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/worker-javascript.js4
-rw-r--r--vendor/assets/javascripts/ace-src-noconflict/worker-xquery.js2
-rw-r--r--vendor/assets/javascripts/branch-graph.js385
-rw-r--r--vendor/assets/javascripts/jquery.blockUI.js590
1216 files changed, 32975 insertions, 17605 deletions
diff --git a/.gitignore b/.gitignore
index 912d61a8f0a..683e6c45a2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,13 +2,15 @@
.rbx/
db/*.sqlite3
db/*.sqlite3-journal
-log/*.log
+log/*.log*
tmp/
.sass-cache/
coverage/*
backups/*
*.swp
public/uploads/
+.ruby-version
+.ruby-gemset
.rvmrc
.rbenv-version
.directory
@@ -20,6 +22,7 @@ config/database.yml
config/initializers/omniauth.rb
config/unicorn.rb
config/resque.yml
+config/aws.yml
db/data.yml
.idea
.DS_Store
@@ -27,3 +30,6 @@ db/data.yml
vendor/bundle/*
rails_best_practices_output.html
doc/code/*
+.secret
+*.log
+public/uploads.*
diff --git a/.rspec b/.rspec
index 53607ea52b7..7488cbe7792 100644
--- a/.rspec
+++ b/.rspec
@@ -1 +1 @@
---colour
+--color --drb
diff --git a/.travis.yml b/.travis.yml
index 6d39488df27..3ed3dec1425 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,6 @@
language: ruby
env:
- - DB=postgresql
- - DB=mysql
+ - DB=mysql TRAVIS=true
before_install:
- sudo apt-get install libicu-dev -y
- gem install charlock_holmes -v="0.6.9"
@@ -9,7 +8,7 @@ branches:
only:
- 'master'
rvm:
- - 1.9.3-p327
+ - 2.0.0
services:
- mysql
- postgresql
diff --git a/CHANGELOG b/CHANGELOG
index a692bbfa8f2..3db03f76ed7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,10 +1,197 @@
+v 6.2.0
+ - Public projects are visible from the outside
+
+v 6.1.0
+ - Project specific IDs for issues, mr, milestones
+ Above items will get a new id and for example all bookmarked issue urls will change.
+ Old issue urls are redirected to the new one if the issue id is too high for an internal id.
+ - Description field added to Merge Request
+ - API: Sudo api calls (Izaak Alpert)
+ - API: Group membership api (Izaak Alpert)
+ - Improved commit diff
+ - Improved large commit handling (Boyan Tabakov)
+ - Rewrite: Init script now less prone to errors and keeps better track of the service (Rovanion Luckey)
+ - Link issues, merge requests, and commits when they reference each other with GFM (Ash Wilson)
+ - Close issues automatically when pushing commits with a special message
+ - Improve user removal from admin area
+ - Invalidate events cache when project was moved
+ - Remove deprecated classes and rake tasks
+ - Add event filter for group and project show pages
+ - Add links to create branch/tag from project home page
+ - Add public-project? checkbox to new-project view
+ - Improved compare page. Added link to proceed into Merge Request
+ - Send email to user when he was added to group
+ - New landing page when you have 0 projects
+
+v 6.0.0
+ - Feature: Replace teams with group membership
+ We introduce group membership in 6.0 as a replacement for teams.
+ The old combination of groups and teams was confusing for a lot of people.
+ And when the members of a team where changed this wasn't reflected in the project permissions.
+ In GitLab 6.0 you will be able to add members to a group with a permission level for each member.
+ These group members will have access to the projects in that group.
+ Any changes to group members will immediately be reflected in the project permissions.
+ You can even have multiple owners for a group, greatly simplifying administration.
+ - Feature: Ability to have multiple owners for group
+ - Feature: Merge Requests between fork and project (Izaak Alpert)
+ - Feature: Generate fingerprint for ssh keys
+ - Feature: Ability to create and remove branches with UI
+ - Feature: Ability to create and remove git tags with UI
+ - Feature: Groups page in profile. You can leave group there
+ - API: Allow login with LDAP credentials
+ - Redesign: project settings navigation
+ - Redesign: snippets area
+ - Redesign: ssh keys page
+ - Redesign: buttons, blocks and other ui elements
+ - Add comment title to rss feed
+ - You can use arrows to navigate at tree view
+ - Add project filter on dashboard
+ - Cache project graph
+ - Drop support of root namespaces
+ - Default theme is classic now
+ - Cache result of methods like authorize_projects, project.team.members etc
+ - Remove $.ready events
+ - Fix onclick events being double binded
+ - Add notification level to group membership
+ - Move all project controllers/views under Projects:: module
+ - Move all profile controllers/views under Profiles:: module
+ - Apply user project limit only for personal projects
+ - Unicorn is default web server again
+ - Store satellites lock files inside satellites dir
+ - Disabled threadsafety mode in rails
+ - Fixed bug with loosing MR comments
+ - Improved MR comments logic
+ - Render readme file for projects in public area
+
+v 5.4.0
+ - Ability to edit own comments
+ - Documentation improvements
+ - Improve dashboard projects page
+ - Fixed nav for empty repos
+ - GitLab Markdown help page
+ - Misspelling fixes
+ - Added support of unicorn and fog gems
+ - Added client list to API doc
+ - Fix PostgreSQL database restoration problem
+ - Increase snippet content column size
+ - allow project import via git:// url
+ - Show participants on issues, including mentions
+ - Notify mentioned users with email
+
+v 5.3.0
+ - Refactored services
+ - Campfire service added
+ - HipChat service added
+ - Fixed bug with LDAP + git over http
+ - Fixed bug with google analytics code being ignored
+ - Improve sign-in page if ldap enabled
+ - Respect newlines in wall messages
+ - Generate the Rails secret token on first run
+ - Rename repo feature
+ - Init.d: remove gitlab.socket on service start
+ - Api: added teams api
+ - Api: Prevent blob content being escaped
+ - Api: Smart deploy key add behaviour
+ - Api: projects/owned.json return user owned project
+ - Fix bug with team assignation on project from #4109
+ - Advanced snippets: public/private, project/personal (Andrew Kulakov)
+ - Repository Graphs (Karlo Nicholas T. Soriano)
+ - Fix dashboard lost if comment on commit
+ - Update gitlab-grack. Fixes issue with --depth option
+ - Fix project events duplicate on project page
+ - Fix postgres error when displaying network graph.
+ - Fix dashboard event filter when navigate via turbolinks
+ - init.d: Ensure socket is removed before starting service
+ - Admin area: Style teams:index, group:show pages
+ - Own page for failed forking
+ - Scrum view for milestone
+
+v 5.2.0
+ - Turbolinks
+ - Git over http with ldap credentials
+ - Diff with better colors and some spacing on the corners
+ - Default values for project features
+ - Fixed huge_commit view
+ - Restyle project clone panel
+ - Move Gitlab::Git code to gitlab_git gem
+ - Move update docs in repo
+ - Requires gitlab-shell v1.4.0
+ - Fixed submodules listing under file tab
+ - Fork feature (Angus MacArthur)
+ - git version check in gitlab:check
+ - Shared deploy keys feature
+ - Ability to generate default labels set for issues
+ - Improve gfm autocomplete (Harold Luo)
+ - Added support for Google Analytics
+ - Code search feature (Javier Castro)
+
+v 5.1.0
+ - You can login with email or username now
+ - Corrected project transfer rollback when repository cannot be moved
+ - Move both repo and wiki when project transfer requested
+ - Admin area: project editing was removed from admin namespace
+ - Access: admin user has now access to any project.
+ - Notification settings
+ - Gitlab::Git set of objects to abstract from grit library
+ - Replace Unicorn web server with Puma
+ - Backup/Restore refactored. Backup dump project wiki too now
+ - Restyled Issues list. Show milestone version in issue row
+ - Restyled Merge Request list
+ - Backup now dump/restore uploads
+ - Improved performance of dashboard (Andrew Kumanyaev)
+ - File history now tracks renames (Akzhan Abdulin)
+ - Drop wiki migration tools
+ - Drop sqlite migration tools
+ - project tagging
+ - Paginate users in API
+ - Restyled network graph (Hiroyuki Sato)
+
+v 5.0.1
+ - Fixed issue with gitlab-grit being overridden by grit
+
v 5.0.0
- Replaced gitolite with gitlab-shell
+ - Removed gitolite-related libraries
+ - State machine added
+ - Setup gitlab as git user
+ - Internal API
+ - Show team tab for empty projects
+ - Import repository feature
+ - Updated rails
+ - Use lambda for scopes
+ - Redesign admin area -> users
+ - Redesign admin area -> user
+ - Secure link to file attachments
+ - Add validations for Group and Team names
+ - Restyle team page for project
+ - Update capybara, rspec-rails, poltergeist to recent versions
+ - Wiki on git using Gollum
+ - Added Solarized Dark theme for code review
+ - Don't show user emails in autocomplete lists, profile pages
+ - Added settings tab for group, team, project
+ - Replace user popup with icons in header
+ - Handle project moving with gitlab-shell
+ - Added select2-rails for selectboxes with ajax data load
+ - Fixed search field on projects page
+ - Added teams to search autocomplete
+ - Move groups and teams on dashboard sidebar to sub-tabs
+ - API: improved return codes and docs. (Felix Gilcher, Sebastian Ziebell)
+ - Redesign wall to be more like chat
+ - Snippets, Wall features are disabled by default for new projects
v 4.2.0
- Teams
- User show page. Via /u/username
- Show help contents on pages for better navigation
+ - Async gitolite calls
+ - added satellites logs
+ - can_create_group, can_create_team booleans for User
+ - Process web hooks async
+ - GFM: Fix images escaped inside links
+ - Network graph improved
+ - Switchable branches for network graph
+ - API: Groups
+ - Fixed project download
v 4.1.0
- Optional Sign-Up
@@ -20,7 +207,7 @@ v 4.1.0
- cleanup rake tasks
- fix backup/restore
- scss cleanup
- - show preview for note images
+ - show preview for note images
- improved network-graph
- get rid of app/roles/
- added new classes Team, Repository
@@ -33,8 +220,8 @@ v 4.1.0
v 4.0.0
- Remove project code and path from API. Use id instead
- - Return valid clonable url to repo for web hook
- - Fixed backup issue
+ - Return valid cloneable url to repo for web hook
+ - Fixed backup issue
- Reorganized settings
- Fixed commits compare
- Refactored scss
@@ -98,15 +285,15 @@ v 3.0.0
- Web Editor
- Fixed bug with gitolite keys
- UI improved
- - Increased perfomance of application
+ - Increased performance of application
- Show user avatar in last commit when browsing Files
- Refactored Gitlab::Merge
- - Use Font Awsome for icons
- - Separate observing of Note and MergeRequestsa
+ - Use Font Awesome for icons
+ - Separate observing of Note and MergeRequests
- Milestone "All Issues" filter
- Fix issue close and reopen button text and styles
- Fix forward/back while browsing Tree hierarchy
- - Show numer of notes for commits and merge requests
+ - Show number of notes for commits and merge requests
- Added support pg from box and update installation doc
- Reject ssh keys that break gitolite
- [API] list one project hook
@@ -125,7 +312,7 @@ v 2.9.0
- added factory_girl
- restyled projects list on dashboard
- ssh keys validation to prevent gitolite crash
- - send notifications if changed premission in project
+ - send notifications if changed permission in project
- scss refactoring. gitlab_bootstrap/ dir
- fix git push http body bigger than 112k problem
- list of labels page under issues tab
@@ -159,7 +346,7 @@ v 2.7.0
- System hooks
- UI improved
- Dashboard events endless scroll
- - Source perfomance increased
+ - Source performance increased
v 2.6.0
- UI polished
@@ -167,7 +354,7 @@ v 2.6.0
- Handle huge commits
- Last Push widget
- Bugfix
- - Better perfomance
+ - Better performance
- Email in resque
- Increased test coverage
- Ability to remove branch with MR accept
@@ -188,7 +375,7 @@ v 2.4.0
- Bootstrap 2.0
- Responsive layout
- Big commits handling
- - Perfomance improved
+ - Performance improved
- Milestones
v 2.3.1
@@ -268,11 +455,11 @@ v 1.0.2
- added adv validation for project path & code
- feature: issues can be sortable
- bugfix
- - username dispalyed on top panel
+ - username displayed on top panel
v 1.0.1
- fixed: with invalid source code for commit
- - fixed: lose branch/tag selection when use tree navigateion
+ - fixed: lose branch/tag selection when use tree navigation
- when history clicked - display path
- bug fix & code cleaning
@@ -289,11 +476,11 @@ v 0.9.4
- authorization improved
- html escaping
- bug fix
- - increassed test coverage
+ - increased test coverage
- design improvements
v 0.9.1
- - increassed test coverage
+ - increased test coverage
- design improvements
- new issue email notification
- updated app name
@@ -301,7 +488,7 @@ v 0.9.1
- issue can be edit
v 0.8.0
- - sytax highlight for main file types
+ - syntax highlight for main file types
- redesign
- stability
- security fixes
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 00304dd3d64..9d9be5bdc21 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,26 +1,75 @@
-# Contact & support
+# Contribute to GitLab
-If you want quick help, head over to our [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq).
-Otherwise you can follow our [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) for a more systematic and thorough guide to solving your issues.
+This guide details how to use issues and pull requests to improve GitLab.
+- [Closing policy for issues and pull requests](#closing-policy-for-issues-and-pull-requests)
+- [Issue tracker](#issue-tracker)
+- [Pull requests](#pull-requests)
+If you want to know how the GitLab team handles contributions have a look at [the GitLab contributing process](PROCESS.md).
-# Contribute to GitLab
+## Closing policy for issues and pull requests
+
+GitLab is a popular open source project and the capacity to deal with issues and pull requests is limited. Out of respect for our volunteers, issues and pull requests not in line with the guidelines listed in this document may be closed without notice.
+
+Please treat our volunteers with courtesy and respect, it will go a long way towards getting your issue resolved.
+
+Issues and pull requests should be in English and contain appropriate language for audiences of all ages.
+
+## Issue tracker
+
+To get support for your particular problem please use the channels as detailed in [the getting help section of the readme](https://github.com/gitlabhq/gitlabhq#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://github.com/gitlabhq/gitlabhq/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 pull 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.
+
+Please send a pull request with a tested solution or a pull 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.
+
+### Issue tracker guidelines
+
+**[Search](https://github.com/gitlabhq/gitlabhq/search?q=&ref=cmdform&type=Issues)** for similar entries before submitting your own, there's a good chance somebody else had the same issue. Show your support with `:+1:` and/or join the discussion. Please submit issues in the following format (as the first post):
-## Recipes
+1. **Summary:** Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
+2. **Steps to reproduce:** How can we reproduce the issue, preferably on the [GitLab Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) (start with: `vagrant destroy && vagrant up && vagrant ssh`)
+3. **Expected behavior:** Describe your issue in detail
+4. **Observed behavior**
+5. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise.
+6. **Output of checks**
+ * Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production`); we will only investigate if the tests are passing
+ * Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md)
+ * Add the last commit sha1 of the GitLab version you used to replicate the issue (obtainable from the help page)
+ * Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
+7. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem
-We collect user submitted installation scripts and config file templates for platforms we don't support officially.
-We believe there is merit in allowing a certain amount of diversity.
-You can get and submit your solution to running/configuring GitLab with your favorite OS/distro, database, web server, cloud hoster, configuration management tool, etc.
+## Pull requests
-Help us improve the collection of [GitLab Recipes](https://github.com/gitlabhq/gitlab-recipes/)
+We welcome pull requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a pull request for are listed with the [status 'accepting merge/pull requests' on our feedback forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome.
+### Pull request guidelines
-## Feature suggestions
+If you can, please submit a pull request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a pull request is as follows:
-Follow the [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) and support other peoples ideas or propose your own.
+1. Fork the project on GitHub
+1. Create a feature branch
+1. Write [tests](README.md#run-the-tests) and code
+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 pull request
+2. [Search for issues](https://github.com/gitlabhq/gitlabhq/search?q=&ref=cmdform&type=Issues) related to your pull request and mention them in the pull request description
+We will accept pull requests if:
-## Code
+* The code has proper tests and all tests pass (or it is a test exposing a failure in existing code)
+* It can be merged without problems (if not please use: `git rebase master`)
+* It does not break any existing functionality
+* It's quality code that 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
+* The description includes a motive for your change and the method you used to achieve it
+* It is not a catch all pull request but rather fixes a specific issue or implements a specific feature
+* It keeps the GitLab code base clean and well structured
+* We think other users will benefit from the same functionality
+* If it makes changes to the UI the pull request should include screenshots
+* It is a single commit (please use `git rebase -i` to squash commits)
-Follow our [Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide) to set you up for hacking on GitLab.
+For examples of feedback on pull requests please look at already [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed).
diff --git a/Gemfile b/Gemfile
index 01696152b62..ccefe0af7a5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,4 +1,4 @@
-source "http://rubygems.org"
+source "https://rubygems.org"
def darwin_only(require_as)
RUBY_PLATFORM.include?('darwin') && require_as
@@ -8,58 +8,64 @@ def linux_only(require_as)
RUBY_PLATFORM.include?('linux') && require_as
end
-gem "rails", "3.2.12"
+gem "rails", "3.2.13"
# Supported DBs
gem "mysql2", group: :mysql
gem "pg", group: :postgres
# Auth
-gem "devise", "~> 2.1.0"
-gem 'omniauth', "~> 1.1.1"
+gem "devise", '~> 2.2'
+gem 'omniauth', "~> 1.1.3"
gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
gem 'omniauth-github'
-# GITLAB patched libs
-gem "grit", git: "https://github.com/gitlabhq/grit.git", ref: '7f35cb98ff17d534a07e3ce6ec3d580f67402837'
-gem 'grack', git: "https://github.com/gitlabhq/grack.git", ref: 'ba46f3b0845c6a09d488ae6abdce6ede37e227e8'
-gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref: '8e6afc2da821354774aa4d1ee8a1aa2082f84a3e'
+# Extracting information from a git repository
+# Provide access to Gitlab::Git library
+gem "gitlab_git", '2.3.1'
-# LDAP Auth
-gem 'gitlab_omniauth-ldap', '1.0.2', require: "omniauth-ldap"
+# Ruby/Rack Git Smart-HTTP Server Handler
+gem 'gitlab-grack', '~> 1.0.1', require: 'grack'
-# Dump db to yml file. Mostly used to migrate from sqlite to mysql
-gem 'gitlab_yaml_db', '1.0.0', require: "yaml_db"
+# LDAP Auth
+gem 'gitlab_omniauth-ldap', '1.0.3', require: "omniauth-ldap"
# Syntax highlighter
-gem "pygments.rb", git: "https://github.com/gitlabhq/pygments.rb.git", branch: "master"
+gem "gitlab-pygments.rb", '~> 0.3.2', require: 'pygments.rb'
+
+# Git Wiki
+gem "gitlab-gollum-lib", "~> 1.0.1", require: 'gollum-lib'
# Language detection
-gem "github-linguist", "~> 2.3.4" , require: "linguist"
+gem "github-linguist", require: "linguist"
# API
-gem "grape", "~> 0.2.1"
+gem "grape", "~> 0.4.1"
+gem "grape-entity", "~> 0.3.0"
# Format dates and times
# based on human-friendly examples
gem "stamp"
+# Enumeration fields
+gem 'enumerize'
+
# Pagination
gem "kaminari", "~> 0.14.1"
# HAML
-gem "haml-rails", "~> 0.3.5"
+gem "haml-rails"
# Files attachments
-gem "carrierwave", "~> 0.7.1"
+gem "carrierwave"
+
+# for aws storage
+gem "fog", "~> 1.3.1", group: :aws
# Authorization
gem "six"
-# Generate Fake data
-gem "ffaker"
-
# Seed data
gem "seed-fu"
@@ -67,19 +73,22 @@ gem "seed-fu"
gem "redcarpet", "~> 2.2.2"
gem "github-markup", "~> 0.7.4", require: 'github/markup'
-# Servers
-gem "unicorn", "~> 4.4.0"
+# Asciidoc to HTML
+gem "asciidoctor"
-# Issue tags
-gem "acts-as-taggable-on", "2.3.3"
+# Application server
+gem "unicorn", '~> 4.6.3', group: :unicorn
-# Decorators
-gem "draper", "~> 0.18.0"
+# State machine
+gem "state_machine"
+
+# Issue tags
+gem "acts-as-taggable-on"
# Background jobs
gem 'slim'
-gem 'sinatra', :require => nil
-gem 'sidekiq', '2.6.4'
+gem 'sinatra', require: nil
+gem 'sidekiq'
# HTTP requests
gem "httparty"
@@ -92,23 +101,44 @@ gem 'settingslogic'
# Misc
gem "foreman"
-gem "git"
+
+# Cache
+gem "redis-rails"
+
+# Campfire integration
+gem 'tinder', '~> 1.9.2'
+
+# HipChat integration
+gem "hipchat", "~> 0.9.0"
+
+# d3
+gem "d3_rails", "~> 3.1.4"
+
+# underscore-rails
+gem "underscore-rails", "~> 1.4.4"
+
+# Sanitize user input
+gem "sanitize"
group :assets do
- gem "sass-rails", "~> 3.2.5"
- gem "coffee-rails", "~> 3.2.2"
- gem "uglifier", "~> 1.3.0"
+ gem "sass-rails"
+ gem "coffee-rails"
+ gem "uglifier"
gem "therubyracer"
+ gem 'turbolinks'
+ gem 'jquery-turbolinks'
- gem 'chosen-rails', "0.9.8"
- gem 'jquery-atwho-rails', "0.1.7"
+ gem 'chosen-rails', "1.0.0"
+ gem 'select2-rails'
+ gem 'jquery-atwho-rails', "0.3.0"
gem "jquery-rails", "2.1.3"
gem "jquery-ui-rails", "2.0.2"
gem "modernizr", "2.6.2"
- gem "raphael-rails", git: "https://github.com/gitlabhq/raphael-rails.git"
- gem 'bootstrap-sass', "2.2.1.1"
- gem "font-awesome-sass-rails", "~> 3.0.0"
+ gem "raphael-rails", "~> 2.1.2"
+ gem 'bootstrap-sass'
+ gem "font-awesome-rails"
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
+ gem "gon"
end
group :development do
@@ -130,16 +160,23 @@ group :development do
end
group :development, :test do
+ gem 'coveralls', require: false
gem 'rails-dev-tweaks'
gem 'spinach-rails'
gem "rspec-rails"
gem "capybara"
gem "pry"
gem "awesome_print"
- gem "database_cleaner", ref: "f89c34300e114be99532f14c115b2799a3380ac6", git: "https://github.com/bmabey/database_cleaner.git"
+ gem "database_cleaner"
gem "launchy"
gem 'factory_girl_rails'
+ # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
+ gem 'minitest', '~> 4.7.0'
+
+ # Generate Fake data
+ gem "ffaker"
+
# Guard
gem 'guard-rspec'
gem 'guard-spinach'
@@ -150,17 +187,20 @@ group :development, :test do
gem 'rb-inotify', require: linux_only('rb-inotify')
# PhantomJS driver for Capybara
- gem 'poltergeist', git: 'https://github.com/jonleighton/poltergeist.git', ref: '5c2e092001074a8cf09f332d3714e9ba150bc8ca'
+ gem 'poltergeist', '~> 1.4.1'
+
+ gem 'spork', '~> 1.0rc'
+ gem 'jasmine'
end
group :test do
gem "simplecov", require: false
- gem "shoulda-matchers", "1.3.0"
+ gem "shoulda-matchers", "~> 2.1.0"
gem 'email_spec'
gem "webmock"
gem 'test_after_commit'
end
group :production do
- gem "gitlab_meta", '5.0'
+ gem "gitlab_meta", '6.0'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 1bc7124fff4..9de7a0f876b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,80 +1,20 @@
GIT
- remote: https://github.com/bmabey/database_cleaner.git
- revision: f89c34300e114be99532f14c115b2799a3380ac6
- ref: f89c34300e114be99532f14c115b2799a3380ac6
- specs:
- database_cleaner (0.9.1)
-
-GIT
remote: https://github.com/ctran/annotate_models.git
- revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
+ revision: 18a4e2eb77c8f3ef695b563e4a7ca45dfede819b
specs:
- annotate (2.6.0.beta1)
+ annotate (2.6.0.beta2)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
-GIT
- remote: https://github.com/gitlabhq/grack.git
- revision: ba46f3b0845c6a09d488ae6abdce6ede37e227e8
- ref: ba46f3b0845c6a09d488ae6abdce6ede37e227e8
- specs:
- grack (1.0.0)
- rack (~> 1.4.1)
-
-GIT
- remote: https://github.com/gitlabhq/grit.git
- revision: 7f35cb98ff17d534a07e3ce6ec3d580f67402837
- ref: 7f35cb98ff17d534a07e3ce6ec3d580f67402837
- specs:
- grit (2.5.0)
- diff-lcs (~> 1.1)
- mime-types (~> 1.15)
- posix-spawn (~> 0.3.6)
-
-GIT
- remote: https://github.com/gitlabhq/grit_ext.git
- revision: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e
- ref: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e
- specs:
- grit_ext (0.6.1)
- charlock_holmes (~> 0.6.9)
-
-GIT
- remote: https://github.com/gitlabhq/pygments.rb.git
- revision: db1da0343adf86b49bdc3add04d02d2e80438d38
- branch: master
- specs:
- pygments.rb (0.3.2)
- posix-spawn (~> 0.3.6)
- yajl-ruby (~> 1.1.0)
-
-GIT
- remote: https://github.com/gitlabhq/raphael-rails.git
- revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
- specs:
- raphael-rails (2.1.0)
-
-GIT
- remote: https://github.com/jonleighton/poltergeist.git
- revision: 5c2e092001074a8cf09f332d3714e9ba150bc8ca
- ref: 5c2e092001074a8cf09f332d3714e9ba150bc8ca
- specs:
- poltergeist (1.0.2)
- capybara (~> 1.1)
- childprocess (~> 0.3)
- faye-websocket (~> 0.4, >= 0.4.4)
- http_parser.rb (~> 0.5.3)
- multi_json (~> 1.0)
-
GEM
- remote: http://rubygems.org/
+ remote: https://rubygems.org/
specs:
- actionmailer (3.2.12)
- actionpack (= 3.2.12)
- mail (~> 2.4.4)
- actionpack (3.2.12)
- activemodel (= 3.2.12)
- activesupport (= 3.2.12)
+ actionmailer (3.2.13)
+ actionpack (= 3.2.13)
+ mail (~> 2.5.3)
+ actionpack (3.2.13)
+ activemodel (= 3.2.13)
+ activesupport (= 3.2.13)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
@@ -82,205 +22,285 @@ GEM
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
- activemodel (3.2.12)
- activesupport (= 3.2.12)
+ activemodel (3.2.13)
+ activesupport (= 3.2.13)
builder (~> 3.0.0)
- activerecord (3.2.12)
- activemodel (= 3.2.12)
- activesupport (= 3.2.12)
+ activerecord (3.2.13)
+ activemodel (= 3.2.13)
+ activesupport (= 3.2.13)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
- activeresource (3.2.12)
- activemodel (= 3.2.12)
- activesupport (= 3.2.12)
- activesupport (3.2.12)
- i18n (~> 0.6)
+ activeresource (3.2.13)
+ activemodel (= 3.2.13)
+ activesupport (= 3.2.13)
+ activesupport (3.2.13)
+ i18n (= 0.6.1)
multi_json (~> 1.0)
- acts-as-taggable-on (2.3.3)
- rails (~> 3.0)
- addressable (2.3.2)
+ acts-as-taggable-on (2.4.1)
+ rails (>= 3, < 5)
+ addressable (2.3.4)
arel (3.0.2)
+ asciidoctor (0.1.3)
awesome_print (1.1.0)
- backports (2.6.5)
- bcrypt-ruby (3.0.1)
- better_errors (0.3.2)
+ backports (3.3.2)
+ bcrypt-ruby (3.1.1)
+ better_errors (0.9.0)
coderay (>= 1.0.0)
- erubis (>= 2.7.0)
- binding_of_caller (0.6.8)
- bootstrap-sass (2.2.1.1)
+ erubis (>= 2.6.6)
+ binding_of_caller (0.7.2)
+ debug_inspector (>= 0.0.1)
+ bootstrap-sass (2.3.2.0)
sass (~> 3.2)
builder (3.0.4)
- capybara (1.1.3)
+ capybara (2.1.0)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
- selenium-webdriver (~> 2.0)
- xpath (~> 0.1.4)
- carrierwave (0.7.1)
+ xpath (~> 2.0)
+ carrierwave (0.8.0)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
- celluloid (0.12.4)
- facter (>= 1.6.12)
+ celluloid (0.14.1)
timers (>= 1.0.0)
- charlock_holmes (0.6.9)
- childprocess (0.3.6)
- ffi (~> 1.0, >= 1.0.6)
- chosen-rails (0.9.8)
- railties (~> 3.0)
- thor (~> 0.14)
- code_analyzer (0.3.1)
+ charlock_holmes (0.6.9.4)
+ childprocess (0.3.9)
+ ffi (~> 1.0, >= 1.0.11)
+ chosen-rails (1.0.0)
+ coffee-rails (>= 3.2)
+ compass-rails (>= 1.0)
+ railties (>= 3.0)
+ sass-rails (>= 3.2)
+ chunky_png (1.2.8)
+ cliver (0.2.1)
+ code_analyzer (0.3.2)
sexp_processor
- coderay (1.0.8)
+ coderay (1.0.9)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script-source
execjs
- coffee-script-source (1.4.0)
+ coffee-script-source (1.6.2)
colored (1.2)
colorize (0.5.8)
- connection_pool (1.0.0)
- crack (0.3.1)
+ compass (0.12.2)
+ chunky_png (~> 1.2)
+ fssm (>= 0.2.7)
+ sass (~> 3.1)
+ compass-rails (1.0.3)
+ compass (>= 0.12.2, < 0.14)
+ connection_pool (1.1.0)
+ coveralls (0.6.7)
+ colorize
+ multi_json (~> 1.3)
+ rest-client
+ simplecov (>= 0.7)
+ thor
+ crack (0.4.0)
+ safe_yaml (~> 0.9.0)
+ d3_rails (3.1.10)
+ railties (>= 3.1.0)
daemons (1.1.9)
- devise (2.1.2)
+ database_cleaner (1.1.1)
+ debug_inspector (0.0.2)
+ descendants_tracker (0.0.1)
+ devise (2.2.5)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.1)
railties (~> 3.1)
warden (~> 1.2.1)
- diff-lcs (1.1.3)
- draper (0.18.0)
- actionpack (~> 3.2)
- activesupport (~> 3.2)
+ diff-lcs (1.2.4)
+ dotenv (0.8.0)
email_spec (1.4.0)
launchy (~> 2.1)
mail (~> 2.2)
+ enumerize (0.6.1)
+ activesupport (>= 3.2)
erubis (2.7.0)
escape_utils (0.2.4)
- eventmachine (1.0.0)
+ eventmachine (1.0.3)
+ excon (0.13.4)
execjs (1.4.0)
multi_json (~> 1.0)
- facter (1.6.17)
- factory_girl (4.1.0)
+ factory_girl (4.2.0)
activesupport (>= 3.0.0)
- factory_girl_rails (4.1.0)
- factory_girl (~> 4.1.0)
+ factory_girl_rails (4.2.1)
+ factory_girl (~> 4.2.0)
railties (>= 3.0.0)
- faraday (0.8.4)
+ faraday (0.8.7)
multipart-post (~> 1.1)
- faye-websocket (0.4.6)
- eventmachine (>= 0.12.0)
- ffaker (1.15.0)
- ffi (1.1.5)
- font-awesome-sass-rails (3.0.0.1)
- railties (>= 3.1.1)
- sass-rails (>= 3.1.1)
- foreman (0.60.2)
+ faraday_middleware (0.9.0)
+ faraday (>= 0.7.4, < 0.9)
+ ffaker (1.16.1)
+ ffi (1.9.0)
+ fog (1.3.1)
+ builder
+ excon (~> 0.13.0)
+ formatador (~> 0.2.0)
+ mime-types
+ multi_json (~> 1.0)
+ net-scp (~> 1.0.4)
+ net-ssh (>= 2.1.3)
+ nokogiri (~> 1.5.0)
+ ruby-hmac
+ font-awesome-rails (3.2.1.2)
+ railties (>= 3.2, < 5.0)
+ foreman (0.63.0)
+ dotenv (>= 0.7)
thor (>= 0.13.6)
+ formatador (0.2.4)
+ fssm (0.2.10)
gemoji (1.2.1)
- gherkin-ruby (0.2.1)
- git (1.2.5)
+ gherkin-ruby (0.3.0)
github-linguist (2.3.4)
charlock_holmes (~> 0.6.6)
escape_utils (~> 0.2.3)
mime-types (~> 1.19)
pygments.rb (>= 0.2.13)
- github-markup (0.7.4)
- gitlab_meta (5.0)
- gitlab_omniauth-ldap (1.0.2)
- net-ldap (~> 0.2.2)
+ github-markdown (0.5.3)
+ github-markup (0.7.5)
+ gitlab-gollum-lib (1.0.1)
+ github-markdown (~> 0.5.3)
+ github-markup (>= 0.7.5, < 1.0.0)
+ gitlab-grit (>= 2.5.1)
+ nokogiri (~> 1.5.9)
+ pygments.rb (~> 0.4.2)
+ sanitize (~> 2.0.3)
+ stringex (~> 1.5.1)
+ gitlab-grack (1.0.1)
+ rack (~> 1.4.1)
+ gitlab-grit (2.6.0)
+ charlock_holmes (~> 0.6.9)
+ diff-lcs (~> 1.1)
+ mime-types (~> 1.15)
+ posix-spawn (~> 0.3.6)
+ gitlab-pygments.rb (0.3.2)
+ posix-spawn (~> 0.3.6)
+ yajl-ruby (~> 1.1.0)
+ gitlab_git (2.3.1)
+ activesupport (~> 3.2.13)
+ github-linguist (~> 2.3.4)
+ gitlab-grit (~> 2.6.0)
+ gitlab_meta (6.0)
+ gitlab_omniauth-ldap (1.0.3)
+ net-ldap (~> 0.3.1)
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1)
- gitlab_yaml_db (1.0.0)
- grape (0.2.2)
+ gon (4.1.1)
+ actionpack (>= 2.3.0)
+ json
+ grape (0.4.1)
activesupport
- hashie (~> 1.2)
+ builder
+ hashie (>= 1.2.0)
multi_json (>= 1.3.2)
- multi_xml
- rack
+ multi_xml (>= 0.5.2)
+ rack (>= 1.3.0)
rack-accept
rack-mount
virtus
+ grape-entity (0.3.0)
+ activesupport
+ multi_json (>= 1.3.2)
growl (1.0.3)
- guard (1.5.4)
- listen (>= 0.4.2)
+ guard (1.8.1)
+ formatador (>= 0.2.4)
+ listen (>= 1.0.0)
lumberjack (>= 1.0.2)
pry (>= 0.9.10)
thor (>= 0.14.6)
- guard-rspec (2.1.2)
- guard (>= 1.1)
- rspec (~> 2.11)
+ guard-rspec (3.0.2)
+ guard (>= 1.8)
+ rspec (~> 2.13)
guard-spinach (0.0.2)
guard (>= 1.1)
spinach
- haml (3.1.7)
- haml-rails (0.3.5)
+ haml (4.0.3)
+ tilt
+ haml-rails (0.4)
actionpack (>= 3.1, < 4.1)
activesupport (>= 3.1, < 4.1)
- haml (~> 3.1)
+ haml (>= 3.1, < 4.1)
railties (>= 3.1, < 4.1)
hashie (1.2.0)
- hike (1.2.1)
+ hike (1.2.3)
+ hipchat (0.9.0)
+ httparty
+ httparty
http_parser.rb (0.5.3)
- httparty (0.9.0)
+ httparty (0.11.0)
multi_json (~> 1.0)
- multi_xml
+ multi_xml (>= 0.5.2)
httpauth (0.2.0)
i18n (0.6.1)
+ jasmine (1.3.2)
+ jasmine-core (~> 1.3.1)
+ rack (~> 1.0)
+ rspec (>= 1.3.1)
+ selenium-webdriver (>= 0.1.3)
+ jasmine-core (1.3.1)
journey (1.0.4)
- jquery-atwho-rails (0.1.7)
+ jquery-atwho-rails (0.3.0)
jquery-rails (2.1.3)
railties (>= 3.1.0, < 5.0)
thor (~> 0.14)
+ jquery-turbolinks (1.0.0)
+ railties (>= 3.1.0)
+ turbolinks
jquery-ui-rails (2.0.2)
jquery-rails
railties (>= 3.1.0)
json (1.7.7)
- jwt (0.1.5)
- multi_json (>= 1.0)
+ jwt (0.1.8)
+ multi_json (>= 1.5)
kaminari (0.14.1)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
- kgio (2.7.4)
- launchy (2.1.2)
+ kgio (2.8.0)
+ launchy (2.3.0)
addressable (~> 2.3)
- letter_opener (1.0.0)
- launchy (>= 2.0.4)
- libv8 (3.3.10.4)
- libwebsocket (0.1.6)
- websocket
- listen (0.5.3)
- lumberjack (1.0.2)
- mail (2.4.4)
- i18n (>= 0.4.0)
+ letter_opener (1.1.1)
+ launchy (~> 2.2)
+ libv8 (3.11.8.17)
+ listen (1.2.2)
+ rb-fsevent (>= 0.9.3)
+ rb-inotify (>= 0.9)
+ rb-kqueue (>= 0.2)
+ lumberjack (1.0.3)
+ mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
method_source (0.8.1)
- mime-types (1.21)
+ mime-types (1.25)
+ minitest (4.7.4)
modernizr (2.6.2)
sprockets (~> 2.0)
- multi_json (1.5.1)
- multi_xml (0.5.1)
- multipart-post (1.1.5)
+ multi_json (1.7.9)
+ multi_xml (0.5.4)
+ multipart-post (1.2.0)
mysql2 (0.3.11)
- net-ldap (0.2.2)
- nokogiri (1.5.5)
+ net-ldap (0.3.1)
+ net-scp (1.0.4)
+ net-ssh (>= 1.99.1)
+ net-ssh (2.6.8)
+ nokogiri (1.5.10)
oauth (0.4.7)
- oauth2 (0.8.0)
+ oauth2 (0.8.1)
faraday (~> 0.8)
httpauth (~> 0.1)
jwt (~> 0.1.4)
multi_json (~> 1.0)
rack (~> 1.2)
- omniauth (1.1.1)
- hashie (~> 1.2)
+ omniauth (1.1.4)
+ hashie (>= 1.2, < 3)
rack
- omniauth-github (1.0.3)
+ omniauth-github (1.1.0)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1)
- omniauth-google-oauth2 (0.1.13)
+ omniauth-google-oauth2 (0.1.19)
omniauth (~> 1.0)
omniauth-oauth2
omniauth-oauth (1.0.1)
@@ -289,92 +309,126 @@ GEM
omniauth-oauth2 (1.1.1)
oauth2 (~> 0.8.0)
omniauth (~> 1.0)
- omniauth-twitter (0.0.14)
+ omniauth-twitter (0.0.17)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
orm_adapter (0.4.0)
- pg (0.14.1)
+ pg (0.15.1)
+ poltergeist (1.4.1)
+ capybara (~> 2.1.0)
+ cliver (~> 0.2.1)
+ multi_json (~> 1.0)
+ websocket-driver (>= 0.2.0)
polyglot (0.3.3)
posix-spawn (0.3.6)
- progressbar (0.12.0)
- pry (0.9.10)
+ pry (0.9.12.2)
coderay (~> 1.0.5)
method_source (~> 0.8)
- slop (~> 3.3.1)
+ slop (~> 3.4)
+ pygments.rb (0.4.2)
+ posix-spawn (~> 0.3.6)
+ yajl-ruby (~> 1.1.0)
pyu-ruby-sasl (0.0.3.3)
- quiet_assets (1.0.1)
- railties (~> 3.1)
+ quiet_assets (1.0.2)
+ railties (>= 3.1, < 5.0)
rack (1.4.5)
rack-accept (0.4.5)
rack (>= 0.4)
rack-cache (1.2)
rack (>= 0.4)
- rack-mini-profiler (0.1.23)
+ rack-mini-profiler (0.1.26)
rack (>= 1.1.3)
rack-mount (0.8.3)
rack (>= 1.0.0)
- rack-protection (1.3.2)
+ rack-protection (1.5.0)
rack
rack-ssl (1.3.3)
rack
rack-test (0.6.2)
rack (>= 1.0)
- rails (3.2.12)
- actionmailer (= 3.2.12)
- actionpack (= 3.2.12)
- activerecord (= 3.2.12)
- activeresource (= 3.2.12)
- activesupport (= 3.2.12)
+ rails (3.2.13)
+ actionmailer (= 3.2.13)
+ actionpack (= 3.2.13)
+ activerecord (= 3.2.13)
+ activeresource (= 3.2.13)
+ activesupport (= 3.2.13)
bundler (~> 1.0)
- railties (= 3.2.12)
+ railties (= 3.2.13)
rails-dev-tweaks (0.6.1)
actionpack (~> 3.1)
railties (~> 3.1)
- rails_best_practices (1.13.2)
+ rails_best_practices (1.13.8)
activesupport
awesome_print
code_analyzer
colored
erubis
i18n
- progressbar
- railties (3.2.12)
- actionpack (= 3.2.12)
- activesupport (= 3.2.12)
+ ruby-progressbar
+ railties (3.2.13)
+ actionpack (= 3.2.13)
+ activesupport (= 3.2.13)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
- raindrops (0.10.0)
- rake (10.0.3)
- rb-fsevent (0.9.2)
- rb-inotify (0.8.8)
+ raindrops (0.11.0)
+ rake (10.1.0)
+ raphael-rails (2.1.2)
+ rb-fsevent (0.9.3)
+ rb-inotify (0.9.0)
+ ffi (>= 0.5.0)
+ rb-kqueue (0.2.0)
ffi (>= 0.5.0)
- rdoc (3.12.1)
+ rdoc (3.12.2)
json (~> 1.4)
redcarpet (2.2.2)
- redis (3.0.2)
- redis-namespace (1.2.1)
+ redis (3.0.4)
+ redis-actionpack (3.2.4)
+ actionpack (~> 3.2.0)
+ redis-rack (~> 1.4.4)
+ redis-store (~> 1.1.4)
+ redis-activesupport (3.2.4)
+ activesupport (~> 3.2.0)
+ redis-store (~> 1.1.0)
+ redis-namespace (1.3.1)
redis (~> 3.0.0)
- rspec (2.12.0)
- rspec-core (~> 2.12.0)
- rspec-expectations (~> 2.12.0)
- rspec-mocks (~> 2.12.0)
- rspec-core (2.12.0)
- rspec-expectations (2.12.0)
- diff-lcs (~> 1.1.3)
- rspec-mocks (2.12.0)
- rspec-rails (2.12.0)
+ redis-rack (1.4.4)
+ rack (~> 1.4.0)
+ redis-store (~> 1.1.4)
+ redis-rails (3.2.4)
+ redis-actionpack (~> 3.2.4)
+ redis-activesupport (~> 3.2.4)
+ redis-store (~> 1.1.4)
+ redis-store (1.1.4)
+ redis (>= 2.2)
+ ref (1.0.5)
+ rest-client (1.6.7)
+ mime-types (>= 1.16)
+ rspec (2.13.0)
+ rspec-core (~> 2.13.0)
+ rspec-expectations (~> 2.13.0)
+ rspec-mocks (~> 2.13.0)
+ rspec-core (2.13.1)
+ rspec-expectations (2.13.0)
+ diff-lcs (>= 1.1.3, < 2.0)
+ rspec-mocks (2.13.1)
+ rspec-rails (2.13.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
- rspec-core (~> 2.12.0)
- rspec-expectations (~> 2.12.0)
- rspec-mocks (~> 2.12.0)
+ rspec-core (~> 2.13.0)
+ rspec-expectations (~> 2.13.0)
+ rspec-mocks (~> 2.13.0)
+ ruby-hmac (0.4.0)
+ ruby-progressbar (1.2.0)
rubyntlm (0.1.1)
rubyzip (0.9.9)
- sass (3.2.5)
- sass-rails (3.2.5)
+ safe_yaml (0.9.3)
+ sanitize (2.0.3)
+ nokogiri (>= 1.4.4, < 1.6)
+ sass (3.2.9)
+ sass-rails (3.2.6)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
@@ -384,78 +438,104 @@ GEM
seed-fu (2.2.0)
activerecord (~> 3.1)
activesupport (~> 3.1)
- selenium-webdriver (2.26.0)
+ select2-rails (3.4.2)
+ sass-rails
+ thor (~> 0.14)
+ selenium-webdriver (2.33.0)
childprocess (>= 0.2.5)
- libwebsocket (~> 0.1.3)
multi_json (~> 1.0)
rubyzip
- settingslogic (2.0.8)
- sexp_processor (4.1.3)
- shoulda-matchers (1.3.0)
+ websocket (~> 1.0.4)
+ settingslogic (2.0.9)
+ sexp_processor (4.2.1)
+ shoulda-matchers (2.1.0)
activesupport (>= 3.0.0)
- sidekiq (2.6.4)
- celluloid (~> 0.12.0)
- connection_pool (~> 1.0)
- multi_json (~> 1)
- redis (~> 3)
+ sidekiq (2.14.0)
+ celluloid (>= 0.14.1)
+ connection_pool (>= 1.0.0)
+ json
+ redis (>= 3.0.4)
redis-namespace
+ simple_oauth (0.1.9)
simplecov (0.7.1)
multi_json (~> 1.0)
simplecov-html (~> 0.7.1)
simplecov-html (0.7.1)
- sinatra (1.3.3)
- rack (~> 1.3, >= 1.3.6)
- rack-protection (~> 1.2)
- tilt (~> 1.3, >= 1.3.3)
+ sinatra (1.4.3)
+ rack (~> 1.4)
+ rack-protection (~> 1.4)
+ tilt (~> 1.3, >= 1.3.4)
six (0.2.0)
- slim (1.3.6)
- temple (~> 0.5.5)
- tilt (~> 1.3.3)
- slop (3.3.3)
- spinach (0.5.2)
- colorize
- gherkin-ruby (~> 0.2.0)
- spinach-rails (0.1.8)
- capybara (~> 1)
+ slim (2.0.0)
+ temple (~> 0.6.5)
+ tilt (~> 1.3, >= 1.3.3)
+ slop (3.4.5)
+ spinach (0.8.3)
+ colorize (= 0.5.8)
+ gherkin-ruby (~> 0.3.0)
+ spinach-rails (0.2.1)
+ capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
+ spork (1.0.0rc2)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
- stamp (0.3.0)
- temple (0.5.5)
- test_after_commit (0.0.1)
- therubyracer (0.10.2)
- libv8 (~> 3.3.10)
- thin (1.5.0)
+ stamp (0.5.0)
+ state_machine (1.2.0)
+ stringex (1.5.1)
+ temple (0.6.5)
+ test_after_commit (0.2.0)
+ therubyracer (0.11.4)
+ libv8 (~> 3.11.8.12)
+ ref
+ thin (1.5.1)
daemons (>= 1.0.9)
eventmachine (>= 0.12.6)
rack (>= 1.0.0)
- thor (0.17.0)
- tilt (1.3.3)
- timers (1.0.2)
- treetop (1.4.12)
+ thor (0.18.1)
+ tilt (1.4.1)
+ timers (1.1.0)
+ tinder (1.9.2)
+ eventmachine (~> 1.0)
+ faraday (~> 0.8)
+ faraday_middleware (~> 0.9)
+ hashie (~> 1.0)
+ json (~> 1.7.5)
+ mime-types (~> 1.19)
+ multi_json (~> 1.5)
+ twitter-stream (~> 0.1)
+ treetop (1.4.14)
polyglot
polyglot (>= 0.3.1)
- tzinfo (0.3.35)
- uglifier (1.3.0)
+ turbolinks (1.2.0)
+ coffee-rails
+ twitter-stream (0.1.16)
+ eventmachine (>= 0.12.8)
+ http_parser.rb (~> 0.5.1)
+ simple_oauth (~> 0.1.4)
+ tzinfo (0.3.37)
+ uglifier (2.1.1)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
- unicorn (4.4.0)
+ underscore-rails (1.4.4)
+ unicorn (4.6.3)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
- virtus (0.5.2)
- backports (~> 2.6.1)
- warden (1.2.1)
+ virtus (0.5.5)
+ backports (~> 3.3)
+ descendants_tracker (~> 0.0.1)
+ warden (1.2.3)
rack (>= 1.0)
- webmock (1.9.0)
+ webmock (1.11.0)
addressable (>= 2.2.7)
- crack (>= 0.1.7)
- websocket (1.0.2)
- xpath (0.1.4)
+ crack (>= 0.3.2)
+ websocket (1.0.7)
+ websocket-driver (0.3.0)
+ xpath (2.0.0)
nokogiri (~> 1.3)
yajl-ruby (1.1.0)
@@ -463,82 +543,98 @@ PLATFORMS
ruby
DEPENDENCIES
- acts-as-taggable-on (= 2.3.3)
+ acts-as-taggable-on
annotate!
+ asciidoctor
awesome_print
better_errors
binding_of_caller
- bootstrap-sass (= 2.2.1.1)
+ bootstrap-sass
capybara
- carrierwave (~> 0.7.1)
- chosen-rails (= 0.9.8)
- coffee-rails (~> 3.2.2)
+ carrierwave
+ chosen-rails (= 1.0.0)
+ coffee-rails
colored
- database_cleaner!
- devise (~> 2.1.0)
- draper (~> 0.18.0)
+ coveralls
+ d3_rails (~> 3.1.4)
+ database_cleaner
+ devise (~> 2.2)
email_spec
+ enumerize
factory_girl_rails
ffaker
- font-awesome-sass-rails (~> 3.0.0)
+ fog (~> 1.3.1)
+ font-awesome-rails
foreman
gemoji (~> 1.2.1)
- git
- github-linguist (~> 2.3.4)
+ github-linguist
github-markup (~> 0.7.4)
- gitlab_meta (= 5.0)
- gitlab_omniauth-ldap (= 1.0.2)
- gitlab_yaml_db (= 1.0.0)
- grack!
- grape (~> 0.2.1)
- grit!
- grit_ext!
+ gitlab-gollum-lib (~> 1.0.1)
+ gitlab-grack (~> 1.0.1)
+ gitlab-pygments.rb (~> 0.3.2)
+ gitlab_git (= 2.3.1)
+ gitlab_meta (= 6.0)
+ gitlab_omniauth-ldap (= 1.0.3)
+ gon
+ grape (~> 0.4.1)
+ grape-entity (~> 0.3.0)
growl
guard-rspec
guard-spinach
- haml-rails (~> 0.3.5)
+ haml-rails
+ hipchat (~> 0.9.0)
httparty
- jquery-atwho-rails (= 0.1.7)
+ jasmine
+ jquery-atwho-rails (= 0.3.0)
jquery-rails (= 2.1.3)
+ jquery-turbolinks
jquery-ui-rails (= 2.0.2)
kaminari (~> 0.14.1)
launchy
letter_opener
+ minitest (~> 4.7.0)
modernizr (= 2.6.2)
mysql2
- omniauth (~> 1.1.1)
+ omniauth (~> 1.1.3)
omniauth-github
omniauth-google-oauth2
omniauth-twitter
pg
- poltergeist!
+ poltergeist (~> 1.4.1)
pry
- pygments.rb!
quiet_assets (~> 1.0.1)
rack-mini-profiler
- rails (= 3.2.12)
+ rails (= 3.2.13)
rails-dev-tweaks
rails_best_practices
- raphael-rails!
+ raphael-rails (~> 2.1.2)
rb-fsevent
rb-inotify
redcarpet (~> 2.2.2)
+ redis-rails
rspec-rails
- sass-rails (~> 3.2.5)
+ sanitize
+ sass-rails
sdoc
seed-fu
+ select2-rails
settingslogic
- shoulda-matchers (= 1.3.0)
- sidekiq (= 2.6.4)
+ shoulda-matchers (~> 2.1.0)
+ sidekiq
simplecov
sinatra
six
slim
spinach-rails
+ spork (~> 1.0rc)
stamp
+ state_machine
test_after_commit
therubyracer
thin
- uglifier (~> 1.3.0)
- unicorn (~> 4.4.0)
+ tinder (~> 1.9.2)
+ turbolinks
+ uglifier
+ underscore-rails (~> 1.4.4)
+ unicorn (~> 4.6.3)
webmock
diff --git a/MAINTENANCE.md b/MAINTENANCE.md
new file mode 100644
index 00000000000..0dca62974c3
--- /dev/null
+++ b/MAINTENANCE.md
@@ -0,0 +1,23 @@
+# GitLab Maintenance Policy
+
+GitLab is a fast moving and evolving project. We currently don't have the
+resources to support many releases concurrently. We support exactly one stable
+release at any given time.
+
+GitLab follows the [Semantic Versioning](http://semver.org/) for its releases:
+`(Major).(Minor).(Patch)`.
+
+* **Major version**: Whenever there is something significant or any backwards
+ incompatible changes are introduced to the public API.
+* **Minor version**: When new, backwards compatible functionality is introduced
+ to the public API or a minor feature is introduced, or when a set of smaller
+ features is rolled out.
+* **Patch number**: When backwards compatible bug fixes are introduced that fix
+ incorrect behavior.
+
+The current stable release will receive security patches and bug fixes
+(eg. `5.0` -> `5.0.1`). Feature releases will mark the next supported stable
+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.
diff --git a/PROCESS.md b/PROCESS.md
new file mode 100644
index 00000000000..668cacc870a
--- /dev/null
+++ b/PROCESS.md
@@ -0,0 +1,105 @@
+# GitLab Contributing Process
+
+## Purpose of describing the contributing process
+
+Below we describe the contributing process to GitLab for two reasons. So that contributors know what to expect from maintainers (possible responses, friendly treatment, etc.). And so that maintainers know what to expect from contributors (use the latest version, ensure that the issue is addressed, friendly treatment, etc.).
+
+## Common actions
+
+### Issue team
+- Looks for issues without workflow labels and triages issue
+- Monitors pull requests
+- Closes invalid issues and pull 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/pull 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/)
+- Monitors all issues/pull 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 pull requests
+
+### Development team
+
+- Responds to issues and pull requests the issue team mentions them in
+- Monitors for new issues in _Awaiting developer action/feedback_ with no developer activity (once a week)
+- Monitors for new pull requests (at least once a week)
+- Manages their work queue by looking at issues and pull 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 pull requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review).
+
+## Priorities of the issue team
+
+1. Mentioning people (critical)
+2. Workflow labels (normal)
+3. Functional labels (minor)
+4. Assigning issues (avoid if possible)
+
+## 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.
+
+## Workflow labels
+
+Workflow labels are purposely not very detailed since that would be hard to keep updated as you would need to reevaluate them after every comment. We optionally use functional labels on demand when want to group related issues to get an overview (for example all issues related to RVM, to tackle them in one go) and to add details to the issue.
+
+- _Awaiting feedback_: Feedback pending from the reporter
+- _Awaiting confirmation of fix_: The issue should already be solved in **master** (generally you can avoid this workflow item and just close the issue right away)
+- _Attached PR_: There is a PR attached and the discussion should happen there
+ - We need to let issues stay in sync with the PR's. We can do this with a "Closing #XXXX" or "Fixes #XXXX" comment in the PR. We can't close the issue when there is a pull request because sometimes a PR is not good and we just close the PR, then the issue must stay.
+- _Awaiting developer action/feedback_: Issue needs to be fixed or clarified by a developer
+
+## Functional labels
+
+These labels describe what development specialities are involved such as: PostgreSQL, UX, LDAP.
+
+## Assigning issues
+
+If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover.
+
+## Label colors
+- Light orange `#fef2c0`: workflow labels for issue team members (awaiting feedback, awaiting confirmation of fix)
+- Bright orange `#eb6420`: workflow labels for core team members (attached PR, awaiting developer action/feedback)
+- Light blue `#82C5FF`: functional labels
+- Green labels `#009800`: issues that can generally be ignored. For example, issues given the following labels normally can be closed immediately:
+ - Feature request (see copy & paste response: [Feature requests](#feature-requests))
+ - Support (see copy & paste response: [Support requests and configuration questions](#support-requests-and-configuration-questions)
+
+## Copy & paste responses
+
+### Improperly formatted issue
+
+Thanks for the issue report. Please reformat your issue to conform to the issue tracker guidelines found in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+
+### Feature requests
+
+Thanks for your interest in GitLab. We don't use the GitHub issue tracker for feature requests. Please use http://feedback.gitlab.com/ for this purpose or create a pull request implementing this feature. Have a look at the \[contribution guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) for more information.
+
+### Issue report for old version
+
+Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+
+### Support requests and configuration questions
+
+Thanks for your interest in GitLab. We don't use the GitHub issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the unofficial #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) for more information.
+
+### Code format
+
+Please use ``` to format console output, logs, and code as it's very hard to read otherwise.
+
+### 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://github.com/gitlabhq/gitlabhq/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://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+
+### Improperly formatted pull request
+
+Thanks for your interest in improving the GitLab codebase! Please update your pull request according to the \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#pull-request-guidelines).
+
+### Inactivity close of an issue
+
+It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+
+### Inactivity close of a pull request
+
+This pull request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the pull request guidelines in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this pull request.
+
diff --git a/Procfile b/Procfile
index 66ca562f131..9003369c938 100644
--- a/Procfile
+++ b/Procfile
@@ -1,2 +1,2 @@
-web: bundle exec unicorn_rails -p $PORT
+web: bundle exec unicorn_rails -p $PORT -E development
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 ee029f9bfcb..ce0f4a8a1c5 100644
--- a/README.md
+++ b/README.md
@@ -1,45 +1,166 @@
-# Welcome to GitLab [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) [![build status](https://secure.travis-ci.org/gitlabhq/grit.png)](https://travis-ci.org/gitlabhq/grit) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq)
+## GitLab: self hosted Git management software
-GitLab is a free project and repository management application
+![logo](https://raw.github.com/gitlabhq/gitlabhq/master/public/gitlab_logo.png)
-[![CI](http://ci.gitlab.org/projects/1/status?ref=master)](http://ci.gitlab.org/projects/1?ref=master)
+![animated-screenshots](https://gist.github.com/fnkr/2f9badd56bfe0ed04ee7/raw/4f48806fbae97f556c2f78d8c2d299c04500cb0d/compiled.gif)
-## Application details
+### GitLab allows you to
+ * keep your code secure on your own server
+ * manage repositories, users and access permissions
+ * communicate through issues, line-comments and wiki pages
+ * perform code review with merge requests
-* based on Ruby on Rails
-* distributed under the MIT License
-* works with gitolite
+### GitLab is
-## Requirements
+* powered by Ruby on Rails
+* completely free and open source (MIT license)
+* used by more than 25.000 organizations to keep their code secure
-* Ubuntu/Debian
+### 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)
+
+* [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
+
+* [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available), gems are updated in major releases of GitLab.
+
+* [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
+
+### 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 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 CI: [Readme](https://github.com/gitlabhq/gitlab-ci/blob/master/README.md) of the GitLab open-source continuous integration server
+
+### Requirements
+
+* Ubuntu/Debian**
* ruby 1.9.3+
-* MySQL
-* git
-* gitolite
-* redis
+* git 1.7.10+
+* redis 2.0+
+* MySQL or PostgreSQL
+
+** More details are in the [requirements doc](doc/install/requirements.md)
+
+### Installation
+
+#### Official production installation
+
+* [Installation guide for a production server](doc/install/installation.md)
+
+
+#### Official development installation
+
+If you want to contribute, please first read our [Contributing Guidelines](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) and then we suggest you to use the Vagrant virtual machine project to get an environment working with all dependencies.
+
+* [Vagrant virtual machine for development](https://github.com/gitlabhq/gitlab-vagrant-vm)
+
+
+#### Unofficial production installations
+
+* [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes) repository with unofficial guides for using GitLab with different software (operating systems, webservers, etc.) than the official version.
+
+* [Installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) public wiki with unofficial guides to install GitLab on different operating systems.
+
+* [BitNami one-click installers](http://bitnami.com/stack/gitlab)
+
+* [TurnKey Linux virtual appliance](http://www.turnkeylinux.org/gitlab)
+
+
+### New versions and upgrading
+
+Since 2011 GitLab is released on the 22nd of every month. Every new release includes an upgrade guide.
+
+* [Upgrade guides](doc/update)
+
+* [Changelog](CHANGELOG)
+
+* 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).
+
+
+### Run in production mode
+
+The Installation guide contains instructions on how to download an init script and run it automatically on boot. You can also start the init script manually:
+
+ sudo service gitlab start
+
+or by directly calling the script
+
+ sudo /etc/init.d/gitlab start
+
+### Run in development mode
+
+Start it with [Foreman](https://github.com/ddollar/foreman)
+
+ bundle exec foreman start -p 3000
+
+or start each component separately
+
+ bundle exec rails s
+ bundle exec rake sidekiq:start
+
+### 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
+
+* [RSpec](http://rspec.info/) unit and functional tests
+
+ All RSpec tests: bundle exec rake spec
+
+ Single RSpec file: bundle exec rspec spec/controllers/commit_controller_spec.rb
+
+* [Spinach](https://github.com/codegram/spinach) integration tests
+
+ All Spinach tests: bundle exec rake spinach
+
+ Single Spinach test: bundle exec spinach features/project/issues/milestones.feature
+
+
+### GitLab interfaces
+
+* [GitLab API](doc/api/README.md)
+
+* [Rake tasks](doc/raketasks)
+
+* [Directory structure](doc/install/structure.md)
+
+* [Databases](doc/install/databases.md)
+
+
+### 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.
-## Install
+* [Unofficial #gitlab IRC on Freenode](http://www.freenode.net/) is another way to get in touch with other GitLab users who may be able to help you.
-Checkout wiki pages for installation information, migration, etc.
+* [Feedback and suggestions forum](http://feedback.gitlab.com) is the place to propose and discuss new features for GitLab.
-## Community
+* [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) describes how to submit pull requests and issues. Pull requests and issues not in line with the guidelines in this document will be closed.
-[Google Group](https://groups.google.com/group/gitlabhq)
+* [Support subscription](http://www.gitlab.com/subscription/) connects you to the knowledge of GitLab experts that will resolve your issues and answer your questions.
-## Contacts
+* [Consultancy](http://www.gitlab.com/consultancy/) allows you hire GitLab experts for installations, upgrades and customizations.
-Twitter:
- * @gitlabhq
- * @dzaporozhets
+### Getting in touch
-Email
+* [Core team](https://github.com/gitlabhq?tab=members)
- * m@gitlabhq.com
+* [Contributors](https://github.com/gitlabhq/gitlabhq/graphs/contributors)
-## Contribute
+* [Leader](https://github.com/randx)
-[Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide)
-Want to help - send a pull request.
-We'll accept good pull requests.
+* [Contact page](http://gitlab.org/contact/)
diff --git a/ROADMAP.md b/ROADMAP.md
deleted file mode 100644
index d148b518b0e..00000000000
--- a/ROADMAP.md
+++ /dev/null
@@ -1,12 +0,0 @@
-## GitLab Roadmap
-
-### v5.0 March 22
-
-* Replace gitolite with gitlab-shell
-* Usability improvements
-* Notification improvements
-
-### v4.2 February 22
-
-* Teams
-
diff --git a/VERSION b/VERSION
index 0ceadf72ef7..79e046f49a5 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-5.0.0pre
+6.2.0.pre
diff --git a/app/assets/fonts/OFL.txt b/app/assets/fonts/OFL.txt
deleted file mode 100644
index 3ce219f0126..00000000000
--- a/app/assets/fonts/OFL.txt
+++ /dev/null
@@ -1,92 +0,0 @@
-Copyright (c) 2010, Jan Gerner (post@yanone.de)
-This Font Software is licensed under the SIL Open Font License, Version 1.1.
-This license is copied below, and is also available with a FAQ at:
-http://scripts.sil.org/OFL
-
-
------------------------------------------------------------
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
------------------------------------------------------------
-
-PREAMBLE
-The goals of the Open Font License (OFL) are to stimulate worldwide
-development of collaborative font projects, to support the font creation
-efforts of academic and linguistic communities, and to provide a free and
-open framework in which fonts may be shared and improved in partnership
-with others.
-
-The OFL allows the licensed fonts to be used, studied, modified and
-redistributed freely as long as they are not sold by themselves. The
-fonts, including any derivative works, can be bundled, embedded,
-redistributed and/or sold with any software provided that any reserved
-names are not used by derivative works. The fonts and derivatives,
-however, cannot be released under any other type of license. The
-requirement for fonts to remain under this license does not apply
-to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-"Font Software" refers to the set of files released by the Copyright
-Holder(s) under this license and clearly marked as such. This may
-include source files, build scripts and documentation.
-
-"Reserved Font Name" refers to any names specified as such after the
-copyright statement(s).
-
-"Original Version" refers to the collection of Font Software components as
-distributed by the Copyright Holder(s).
-
-"Modified Version" refers to any derivative made by adding to, deleting,
-or substituting -- in part or in whole -- any of the components of the
-Original Version, by changing formats or by porting the Font Software to a
-new environment.
-
-"Author" refers to any designer, engineer, programmer, technical
-writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Font Software, to use, study, copy, merge, embed, modify,
-redistribute, and sell modified and unmodified copies of the Font
-Software, subject to the following conditions:
-
-1) Neither the Font Software nor any of its individual components,
-in Original or Modified Versions, may be sold by itself.
-
-2) Original or Modified Versions of the Font Software may be bundled,
-redistributed and/or sold with any software, provided that each copy
-contains the above copyright notice and this license. These can be
-included either as stand-alone text files, human-readable headers or
-in the appropriate machine-readable metadata fields within text or
-binary files as long as those fields can be easily viewed by the user.
-
-3) No Modified Version of the Font Software may use the Reserved Font
-Name(s) unless explicit written permission is granted by the corresponding
-Copyright Holder. This restriction only applies to the primary font name as
-presented to the users.
-
-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
-Software shall not be used to promote, endorse or advertise any
-Modified Version, except to acknowledge the contribution(s) of the
-Copyright Holder(s) and the Author(s) or with their explicit written
-permission.
-
-5) The Font Software, modified or unmodified, in part or in whole,
-must be distributed entirely under this license, and must not be
-distributed under any other license. The requirement for fonts to
-remain under this license does not apply to any document created
-using the Font Software.
-
-TERMINATION
-This license becomes null and void if any of the above conditions are
-not met.
-
-DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
-OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/app/assets/fonts/YanoneKaffeesatz-Light.ttf b/app/assets/fonts/YanoneKaffeesatz-Light.ttf
deleted file mode 100644
index 5026d3bdbe2..00000000000
--- a/app/assets/fonts/YanoneKaffeesatz-Light.ttf
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/dark.png b/app/assets/images/dark-scheme-preview.png
index 055a9069b63..055a9069b63 100644
--- a/app/assets/images/dark.png
+++ b/app/assets/images/dark-scheme-preview.png
Binary files differ
diff --git a/app/assets/images/login-logo.png b/app/assets/images/login-logo.png
deleted file mode 100644
index 8c064b12dd0..00000000000
--- a/app/assets/images/login-logo.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png
new file mode 100644
index 00000000000..6567f2e5463
--- /dev/null
+++ b/app/assets/images/logo-black.png
Binary files differ
diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png
new file mode 100644
index 00000000000..a63fb1c9c0a
--- /dev/null
+++ b/app/assets/images/logo-white.png
Binary files differ
diff --git a/app/assets/images/logo_dark.png b/app/assets/images/logo_dark.png
deleted file mode 100644
index 4a3e3391599..00000000000
--- a/app/assets/images/logo_dark.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/logo_white.png b/app/assets/images/logo_white.png
deleted file mode 100644
index e3415816558..00000000000
--- a/app/assets/images/logo_white.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/merge.png b/app/assets/images/merge.png
deleted file mode 100644
index 4a6bb2e154d..00000000000
--- a/app/assets/images/merge.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/monokai-scheme-preview.png b/app/assets/images/monokai-scheme-preview.png
new file mode 100644
index 00000000000..9477941778e
--- /dev/null
+++ b/app/assets/images/monokai-scheme-preview.png
Binary files differ
diff --git a/app/assets/images/solarized-dark-scheme-preview.png b/app/assets/images/solarized-dark-scheme-preview.png
new file mode 100644
index 00000000000..728964bc4c8
--- /dev/null
+++ b/app/assets/images/solarized-dark-scheme-preview.png
Binary files differ
diff --git a/app/assets/images/white.png b/app/assets/images/white-scheme-preview.png
index 67eb8763044..67eb8763044 100644
--- a/app/assets/images/white.png
+++ b/app/assets/images/white-scheme-preview.png
Binary files differ
diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee
new file mode 100644
index 00000000000..fdefbfb92bd
--- /dev/null
+++ b/app/assets/javascripts/activities.js.coffee
@@ -0,0 +1,31 @@
+class Activities
+ constructor: ->
+ Pager.init 20, true
+ $(".event_filter_link").bind "click", (event) =>
+ event.preventDefault()
+ @toggleFilter($(event.currentTarget))
+ @reloadActivities()
+
+ reloadActivities: ->
+ $(".content_list").html ''
+ Pager.init 20, true
+
+
+ toggleFilter: (sender) ->
+ sender.parent().toggleClass "inactive"
+ event_filters = $.cookie("event_filter")
+ filter = sender.attr("id").split("_")[0]
+ if event_filters
+ event_filters = event_filters.split(",")
+ else
+ event_filters = new Array()
+
+ index = event_filters.indexOf(filter)
+ if index is -1
+ event_filters.push filter
+ else
+ event_filters.splice index, 1
+
+ $.cookie "event_filter", event_filters.join(","), { path: '/' }
+
+@Activities = Activities
diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee
index 1dafdf4bd8b..6230fe7f93f 100644
--- a/app/assets/javascripts/admin.js.coffee
+++ b/app/assets/javascripts/admin.js.coffee
@@ -1,17 +1,38 @@
-$ ->
- $('input#user_force_random_password').on 'change', (elem) ->
- elems = $('#user_password, #user_password_confirmation')
-
- if $(@).attr 'checked'
- elems.val('').attr 'disabled', true
- else
- elems.removeAttr 'disabled'
-
- $('.log-tabs a').click (e) ->
- e.preventDefault()
- $(this).tab('show')
-
- $('.log-bottom').click (e) ->
- e.preventDefault()
- visible_log = $(".file_content:visible")
- visible_log.animate({ scrollTop: visible_log.find('ol').height() }, "fast")
+class Admin
+ constructor: ->
+ $('input#user_force_random_password').on 'change', (elem) ->
+ elems = $('#user_password, #user_password_confirmation')
+
+ if $(@).attr 'checked'
+ elems.val('').attr 'disabled', true
+ else
+ elems.removeAttr 'disabled'
+
+ $('.log-tabs a').click (e) ->
+ e.preventDefault()
+ $(this).tab('show')
+
+ $('.log-bottom').click (e) ->
+ e.preventDefault()
+ visible_log = $(".file-content:visible")
+ visible_log.animate({ scrollTop: visible_log.find('ol').height() }, "fast")
+
+ modal = $('.change-owner-holder')
+
+ $('.change-owner-link').bind "click", (e) ->
+ e.preventDefault()
+ $(this).hide()
+ modal.show()
+
+ $('.change-owner-cancel-link').bind "click", (e) ->
+ e.preventDefault()
+ modal.hide()
+ $('.change-owner-link').show()
+
+ $('li.users_project').bind 'ajax:success', ->
+ Turbolinks.visit(location.href)
+
+ $('li.users_group').bind 'ajax:success', ->
+ Turbolinks.visit(location.href)
+
+@Admin = Admin
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
new file mode 100644
index 00000000000..db80e7b0f3c
--- /dev/null
+++ b/app/assets/javascripts/api.js.coffee
@@ -0,0 +1,54 @@
+@Api =
+ users_path: "/api/:version/users.json"
+ user_path: "/api/:version/users/:id.json"
+ notes_path: "/api/:version/projects/:id/notes.json"
+
+ # Get 20 (depends on api) recent notes
+ # and sort the ascending from oldest to newest
+ notes: (project_id, callback) ->
+ url = Api.buildUrl(Api.notes_path)
+ url = url.replace(':id', project_id)
+
+ $.ajax(
+ url: url,
+ data:
+ private_token: gon.api_token
+ gfm: true
+ recent: true
+ dataType: "json"
+ ).done (notes) ->
+ notes.sort (a, b) ->
+ return a.id - b.id
+ callback(notes)
+
+ user: (user_id, callback) ->
+ url = Api.buildUrl(Api.user_path)
+ url = url.replace(':id', user_id)
+
+ $.ajax(
+ url: url
+ data:
+ private_token: gon.api_token
+ dataType: "json"
+ ).done (user) ->
+ callback(user)
+
+ # Return users list. Filtered by query
+ # Only active users retrieved
+ users: (query, callback) ->
+ url = Api.buildUrl(Api.users_path)
+
+ $.ajax(
+ url: url
+ data:
+ private_token: gon.api_token
+ search: query
+ per_page: 20
+ active: true
+ dataType: "json"
+ ).done (users) ->
+ callback(users)
+
+ buildUrl: (url) ->
+ url = gon.relative_url_root + url if gon.relative_url_root?
+ return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 49effdf9c15..0767b82032d 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -14,12 +14,18 @@
//= require jquery.waitforimages
//= require jquery.atwho
//= require jquery.scrollto
+//= require jquery.blockUI
+//= require turbolinks
+//= require jquery.turbolinks
//= require bootstrap
//= require modernizr
//= require chosen-jquery
+//= require select2
//= require raphael
//= require g.raphael-min
//= require g.bar-min
//= require branch-graph
//= require ace-src-noconflict/ace
//= require_tree .
+//= require d3
+//= require underscore
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee
index 3fefbf8e121..7e438c51c1c 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.coffee
+++ b/app/assets/javascripts/behaviors/toggler_behavior.coffee
@@ -3,3 +3,15 @@ $ ->
container = $(@).closest(".js-toggler-container")
container.toggleClass("on")
+
+ $("body").on "click", ".js-toggle-visibility-link", (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/blob.js.coffee b/app/assets/javascripts/blob.js.coffee
new file mode 100644
index 00000000000..03e280f8976
--- /dev/null
+++ b/app/assets/javascripts/blob.js.coffee
@@ -0,0 +1,24 @@
+class BlobView
+ constructor: ->
+ # See if there are lines selected
+ # "#L12" and "#L34-56" supported
+ highlightBlobLines = ->
+ if window.location.hash isnt ""
+ matches = window.location.hash.match(/\#L(\d+)(\-(\d+))?/)
+ first_line = parseInt(matches?[1])
+ last_line = parseInt(matches?[3])
+
+ unless isNaN first_line
+ last_line = first_line if isNaN(last_line)
+ $("#tree-content-holder .highlight .line").removeClass("hll")
+ $("#LC#{line}").addClass("hll") for line in [first_line..last_line]
+ $("#L#{first_line}").ScrollTo()
+
+ # Highlight the correct lines on load
+ highlightBlobLines()
+
+ # Highlight the correct lines when the hash part of the URL changes
+ $(window).on 'hashchange', highlightBlobLines
+
+
+@BlobView = BlobView
diff --git a/app/assets/javascripts/branch-graph.js.coffee b/app/assets/javascripts/branch-graph.js.coffee
new file mode 100644
index 00000000000..318538509a5
--- /dev/null
+++ b/app/assets/javascripts/branch-graph.js.coffee
@@ -0,0 +1,326 @@
+class BranchGraph
+ constructor: (@element, @options) ->
+ @preparedCommits = {}
+ @mtime = 0
+ @mspace = 0
+ @parents = {}
+ @colors = ["#000"]
+ @offsetX = 150
+ @offsetY = 20
+ @unitTime = 30
+ @unitSpace = 10
+ @prev_start = -1
+ @load()
+
+ load: ->
+ $.ajax
+ url: @options.url
+ method: "get"
+ dataType: "json"
+ success: $.proxy((data) ->
+ $(".loading", @element).hide()
+ @prepareData data.days, data.commits
+ @buildGraph()
+ , this)
+
+ prepareData: (@days, @commits) ->
+ @collectParents()
+ @graphHeight = $(@element).height()
+ @graphWidth = $(@element).width()
+ ch = Math.max(@graphHeight, @offsetY + @unitTime * @mtime + 150)
+ cw = Math.max(@graphWidth, @offsetX + @unitSpace * @mspace + 300)
+ @r = Raphael(@element.get(0), cw, ch)
+ @top = @r.set()
+ @barHeight = Math.max(@graphHeight, @unitTime * @days.length + 320)
+
+ for c in @commits
+ c.isParent = true if c.id of @parents
+ @preparedCommits[c.id] = c
+ @markCommit(c)
+
+ @collectColors()
+
+ collectParents: ->
+ for c in @commits
+ @mtime = Math.max(@mtime, c.time)
+ @mspace = Math.max(@mspace, c.space)
+ for p in c.parents
+ @parents[p[0]] = true
+ @mspace = Math.max(@mspace, p[1])
+
+ collectColors: ->
+ k = 0
+ while k < @mspace
+ @colors.push Raphael.getColor(.8)
+ # Skipping a few colors in the spectrum to get more contrast between colors
+ Raphael.getColor()
+ Raphael.getColor()
+ k++
+
+ buildGraph: ->
+ r = @r
+ cuday = 0
+ cumonth = ""
+
+ r.rect(0, 0, 40, @barHeight).attr fill: "#222"
+ r.rect(40, 0, 30, @barHeight).attr fill: "#444"
+
+ for day, mm in @days
+ if cuday isnt day[0]
+ # Dates
+ r.text(55, @offsetY + @unitTime * mm, day[0])
+ .attr(
+ font: "12px Monaco, monospace"
+ fill: "#BBB"
+ )
+ cuday = day[0]
+
+ if cumonth isnt day[1]
+ # Months
+ r.text(20, @offsetY + @unitTime * mm, day[1])
+ .attr(
+ font: "12px Monaco, monospace"
+ fill: "#EEE"
+ )
+ cumonth = day[1]
+
+ @renderPartialGraph()
+
+ @bindEvents()
+
+ renderPartialGraph: ->
+ start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10
+ start = 0 if start < 0
+ end = start + 40
+ end = @commits.length if @commits.length < end
+
+ if @prev_start == -1 or Math.abs(@prev_start - start) > 10
+ i = start
+
+ @prev_start = start
+
+ while i < end
+ commit = @commits[i]
+ i += 1
+
+ if commit.hasDrawn isnt true
+ x = @offsetX + @unitSpace * (@mspace - commit.space)
+ y = @offsetY + @unitTime * commit.time
+
+ @drawDot(x, y, commit)
+
+ @drawLines(x, y, commit)
+
+ @appendLabel(x, y, commit)
+
+ @appendAnchor(x, y, commit)
+
+ commit.hasDrawn = true
+
+ @top.toFront()
+
+ bindEvents: ->
+ drag = {}
+ element = @element
+
+ $(element).scroll (event) =>
+ @renderPartialGraph()
+
+ $(window).on
+ keydown: (event) =>
+ # left
+ element.scrollLeft element.scrollLeft() - 50 if event.keyCode is 37
+ # top
+ element.scrollTop element.scrollTop() - 50 if event.keyCode is 38
+ # right
+ element.scrollLeft element.scrollLeft() + 50 if event.keyCode is 39
+ # bottom
+ element.scrollTop element.scrollTop() + 50 if event.keyCode is 40
+ @renderPartialGraph()
+
+ appendLabel: (x, y, commit) ->
+ return unless commit.refs
+
+ r = @r
+ shortrefs = commit.refs
+ # Truncate if longer than 15 chars
+ shortrefs = shortrefs.substr(0, 15) + "…" if shortrefs.length > 17
+ text = r.text(x + 4, y, shortrefs).attr(
+ "text-anchor": "start"
+ font: "10px Monaco, monospace"
+ fill: "#FFF"
+ title: commit.refs
+ )
+ textbox = text.getBBox()
+ # Create rectangle based on the size of the textbox
+ rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr(
+ fill: "#000"
+ "fill-opacity": .5
+ stroke: "none"
+ )
+ triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr(
+ fill: "#000"
+ "fill-opacity": .5
+ stroke: "none"
+ )
+
+ label = r.set(rect, text)
+ label.transform(["t", -rect.getBBox().width - 15, 0])
+
+ # Set text to front
+ text.toFront()
+
+ appendAnchor: (x, y, commit) ->
+ r = @r
+ top = @top
+ options = @options
+ anchor = r.circle(x, y, 10).attr(
+ fill: "#000"
+ opacity: 0
+ cursor: "pointer"
+ ).click(->
+ window.open options.commit_url.replace("%s", commit.id), "_blank"
+ ).hover(->
+ @tooltip = r.commitTooltip(x + 5, y, commit)
+ top.push @tooltip.insertBefore(this)
+ , ->
+ @tooltip and @tooltip.remove() and delete @tooltip
+ )
+ top.push anchor
+
+ drawDot: (x, y, commit) ->
+ r = @r
+ r.circle(x, y, 3).attr(
+ fill: @colors[commit.space]
+ stroke: "none"
+ )
+ r.rect(@offsetX + @unitSpace * @mspace + 10, y - 10, 20, 20).attr(
+ fill: "url(#{commit.author.icon})"
+ stroke: @colors[commit.space]
+ "stroke-width": 2
+ )
+ r.text(@offsetX + @unitSpace * @mspace + 35, y, commit.message.split("\n")[0]).attr(
+ "text-anchor": "start"
+ font: "14px Monaco, monospace"
+ )
+
+ drawLines: (x, y, commit) ->
+ r = @r
+ for parent, i in commit.parents
+ parentCommit = @preparedCommits[parent[0]]
+ parentY = @offsetY + @unitTime * parentCommit.time
+ parentX1 = @offsetX + @unitSpace * (@mspace - parentCommit.space)
+ parentX2 = @offsetX + @unitSpace * (@mspace - parent[1])
+
+ # Set line color
+ if parentCommit.space <= commit.space
+ color = @colors[commit.space]
+
+ else
+ color = @colors[parentCommit.space]
+
+ # Build line shape
+ if parent[1] is commit.space
+ offset = [0, 5]
+ arrow = "l-2,5,4,0,-2,-5,0,5"
+
+ else if parent[1] < commit.space
+ offset = [3, 3]
+ arrow = "l5,0,-2,4,-3,-4,4,2"
+
+ else
+ offset = [-3, 3]
+ arrow = "l-5,0,2,4,3,-4,-4,2"
+
+ # Start point
+ route = ["M", x + offset[0], y + offset[1]]
+
+ # Add arrow if not first parent
+ if i > 0
+ route.push(arrow)
+
+ # Circumvent if overlap
+ if commit.space isnt parentCommit.space or commit.space isnt parent[1]
+ route.push(
+ "L", parentX2, y + 10,
+ "L", parentX2, parentY - 5,
+ )
+
+ # End point
+ route.push("L", parentX1, parentY)
+
+ r
+ .path(route)
+ .attr(
+ stroke: color
+ "stroke-width": 2)
+
+ markCommit: (commit) ->
+ if commit.id is @options.commit_id
+ r = @r
+ x = @offsetX + @unitSpace * (@mspace - commit.space)
+ y = @offsetY + @unitTime * commit.time
+ r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr(
+ fill: "#000"
+ "fill-opacity": .5
+ stroke: "none"
+ )
+ # Displayed in the center
+ @element.scrollTop(y - @graphHeight / 2)
+
+Raphael::commitTooltip = (x, y, commit) ->
+ boxWidth = 300
+ boxHeight = 200
+ icon = @image(commit.author.icon, x, y, 20, 20)
+ nameText = @text(x + 25, y + 10, commit.author.name)
+ idText = @text(x, y + 35, commit.id)
+ messageText = @text(x, y + 50, commit.message)
+ textSet = @set(icon, nameText, idText, messageText).attr(
+ "text-anchor": "start"
+ font: "12px Monaco, monospace"
+ )
+ nameText.attr(
+ font: "14px Arial"
+ "font-weight": "bold"
+ )
+
+ idText.attr fill: "#AAA"
+ @textWrap messageText, boxWidth - 50
+ rect = @rect(x - 10, y - 10, boxWidth, 100, 4).attr(
+ fill: "#FFF"
+ stroke: "#000"
+ "stroke-linecap": "round"
+ "stroke-width": 2
+ )
+ tooltip = @set(rect, textSet)
+ rect.attr(
+ height: tooltip.getBBox().height + 10
+ width: tooltip.getBBox().width + 10
+ )
+
+ tooltip.transform ["t", 20, 20]
+ tooltip
+
+Raphael::textWrap = (t, width) ->
+ content = t.attr("text")
+ abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ t.attr text: abc
+ letterWidth = t.getBBox().width / abc.length
+ t.attr text: content
+ words = content.split(" ")
+ x = 0
+ s = []
+
+ for word in words
+ if x + (word.length * letterWidth) > width
+ s.push "\n"
+ x = 0
+ x += word.length * letterWidth
+ s.push word + " "
+
+ t.attr text: s.join("")
+ b = t.getBBox()
+ h = Math.abs(b.y2) - Math.abs(b.y) + 1
+ t.attr y: b.y + h
+
+@BranchGraph = BranchGraph
diff --git a/app/assets/javascripts/chart.js.coffee b/app/assets/javascripts/chart.js.coffee
new file mode 100644
index 00000000000..989f48e5e75
--- /dev/null
+++ b/app/assets/javascripts/chart.js.coffee
@@ -0,0 +1,21 @@
+@Chart =
+ labels: []
+ values: []
+
+ init: (labels, values, title) ->
+ r = Raphael('activity-chart')
+
+ fin = ->
+ @flag = r.popup(@bar.x, @bar.y, @bar.value or "0").insertBefore(this)
+
+ fout = ->
+ @flag.animate
+ opacity: 0, 300, -> @remove()
+
+ r.text(160, 10, title).attr font: "13px sans-serif"
+ r.barchart(
+ 10, 20, 560, 200,
+ [values],
+ {colors:["#456"]}
+ ).label(labels, true)
+ .hover(fin, fout)
diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee
new file mode 100644
index 00000000000..9f55a1e6368
--- /dev/null
+++ b/app/assets/javascripts/commit.js.coffee
@@ -0,0 +1,6 @@
+class Commit
+ constructor: ->
+ $('.files .file').each ->
+ new CommitFile(this)
+
+@Commit = Commit
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
index 47d6fcf8089..de4c06a2728 100644
--- a/app/assets/javascripts/commits.js.coffee
+++ b/app/assets/javascripts/commits.js.coffee
@@ -23,7 +23,7 @@ class CommitsList
@data.offset = limit
this.initLoadMore()
- this.showProgress();
+ this.showProgress()
@getOld: ->
this.showProgress()
@@ -41,7 +41,8 @@ class CommitsList
else
@disable = true
- @initLoadMore: ->
+ @initLoadMore: ->
+ $(document).unbind('scroll')
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
@@ -51,4 +52,4 @@ class CommitsList
callback: =>
this.getOld()
-this.CommitsList = CommitsList \ No newline at end of file
+this.CommitsList = CommitsList
diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee
index 6171e0d50fd..d2bd9e7362b 100644
--- a/app/assets/javascripts/dashboard.js.coffee
+++ b/app/assets/javascripts/dashboard.js.coffee
@@ -1,27 +1,33 @@
-window.dashboardPage = ->
- Pager.init 20, true
- $(".event_filter_link").bind "click", (event) ->
- event.preventDefault()
- toggleFilter $(this)
- reloadActivities()
-
-reloadActivities = ->
- $(".content_list").html ''
- Pager.init 20, true
-
-toggleFilter = (sender) ->
- sender.parent().toggleClass "inactive"
- event_filters = $.cookie("event_filter")
- filter = sender.attr("id").split("_")[0]
- if event_filters
- event_filters = event_filters.split(",")
- else
- event_filters = new Array()
-
- index = event_filters.indexOf(filter)
- if index is -1
- event_filters.push filter
- else
- event_filters.splice index, 1
-
- $.cookie "event_filter", event_filters.join(",")
+class Dashboard
+ constructor: ->
+ @initSidebarTab()
+
+ $(".dash-filter").keyup ->
+ terms = $(this).val()
+ uiBox = $(this).parents('.ui-box').first()
+ if terms == "" || terms == undefined
+ uiBox.find(".dash-list li").show()
+ else
+ uiBox.find(".dash-list li").each (index) ->
+ name = $(this).find(".filter-title").text()
+
+ if name.toLowerCase().search(terms.toLowerCase()) == -1
+ $(this).hide()
+ else
+ $(this).show()
+
+
+
+ initSidebarTab: ->
+ key = "dashboard_sidebar_filter"
+
+ # store selection in cookie
+ $('.dash-sidebar-tabs a').on 'click', (e) ->
+ $.cookie(key, $(e.target).attr('id'))
+
+ # show tab from cookie
+ sidebar_filter = $.cookie(key)
+ $("#" + sidebar_filter).tab('show') if sidebar_filter
+
+
+@Dashboard = Dashboard
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
new file mode 100644
index 00000000000..e264e281309
--- /dev/null
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -0,0 +1,51 @@
+$ ->
+ new Dispatcher()
+
+class Dispatcher
+ constructor: () ->
+ @initSearch()
+ @initPageScripts()
+
+ initPageScripts: ->
+ page = $('body').attr('data-page')
+ project_id = $('body').attr('data-project-id')
+
+ unless page
+ return false
+
+ path = page.split(':')
+
+ switch page
+ when 'projects:issues:index'
+ Issues.init()
+ when 'projects:issues:new', 'projects:merge_requests:new'
+ GitLab.GfmAutoComplete.setup()
+ when 'dashboard:show'
+ new Dashboard()
+ new Activities()
+ when 'projects:commit:show'
+ new Commit()
+ when 'groups:show', 'projects:show'
+ new Activities()
+ when 'projects:new', 'projects:edit'
+ new Project()
+ when 'projects:walls:show'
+ new Wall(project_id)
+ when 'projects:teams:members:index'
+ new TeamMembers()
+ when 'groups:members'
+ new GroupMembers()
+ when 'projects:tree:show'
+ new TreeView()
+ when 'projects:blob:show'
+ new BlobView()
+
+ switch path.first()
+ when 'admin' then new Admin()
+ when 'projects'
+ new Wikis() if path[1] == 'wikis'
+
+
+ initSearch: ->
+ autocomplete_json = $('.search-autocomplete-json').data('autocomplete-opts')
+ new SearchAutocomplete(autocomplete_json)
diff --git a/app/assets/javascripts/extensions/jquery.js.coffee b/app/assets/javascripts/extensions/jquery.js.coffee
new file mode 100644
index 00000000000..40fb6cb9fc3
--- /dev/null
+++ b/app/assets/javascripts/extensions/jquery.js.coffee
@@ -0,0 +1,13 @@
+$.fn.showAndHide = ->
+ $(@).show().
+ delay(3000).
+ fadeOut()
+
+$.fn.enableButton = ->
+ $(@).removeAttr('disabled').
+ removeClass('disabled')
+
+$.fn.disableButton = ->
+ $(@).attr('disabled', 'disabled').
+ addClass('disabled')
+
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
new file mode 100644
index 00000000000..f8b7789884f
--- /dev/null
+++ b/app/assets/javascripts/flash.js.coffee
@@ -0,0 +1,15 @@
+class Flash
+ constructor: (message, type)->
+ flash = $(".flash-container")
+ flash.html("")
+
+ $('<div/>',
+ class: "flash-#{type}",
+ text: message
+ ).appendTo(".flash-container")
+
+ flash.click -> $(@).fadeOut()
+ flash.show()
+ setTimeout (-> flash.fadeOut()), 5000
+
+@Flash = Flash
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 1cc9d34dd80..77091da8f61 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -2,37 +2,55 @@
window.GitLab ?= {}
GitLab.GfmAutoComplete =
+ # private_token: ''
+ dataSource: ''
# Emoji
Emoji:
- data: []
+ assetBase: ''
template: '<li data-value="${insert}">${name} <img alt="${name}" height="20" src="${image}" width="20" /></li>'
# Team Members
Members:
- data: []
- url: ''
- params:
- private_token: ''
template: '<li data-value="${username}">${username} <small>${name}</small></li>'
+ Issues:
+ template: '<li data-value="${id}"><small>${id}</small> ${title} </li>'
+
# Add GFM auto-completion to all input fields, that accept GFM input.
setup: ->
input = $('.js-gfm-input')
# Emoji
- input.atWho ':',
- data: @Emoji.data
+ input.atwho
+ at: ':'
tpl: @Emoji.template
+ callbacks:
+ before_save: (emojis) =>
+ $.map emojis, (em) => name: em, insert: em+ ':', image: "#{@Emoji.assetBase}/#{em}.png"
# Team Members
- input.atWho '@',
+ input.atwho
+ at: '@'
tpl: @Members.template
- callback: (query, callback) =>
- request_params = $.extend({}, @Members.params, query: query)
- $.getJSON(@Members.url, request_params).done (members) =>
- new_members_data = $.map(members, (m) ->
- username: m.username,
- name: m.name
- )
- callback(new_members_data)
+ search_key: 'search'
+ callbacks:
+ before_save: (members) =>
+ $.map members, (m) => name: m.name, username: m.username, search: "#{m.username} #{m.name}"
+
+ input.atwho
+ at: '#'
+ alias: 'issues'
+ search_key: 'search'
+ tpl: @Issues.template
+ callbacks:
+ before_save: (issues) ->
+ $.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}"
+ input.one "focus", =>
+ $.getJSON(@dataSource).done (data) ->
+ # load members
+ input.atwho 'load', "@", data.members
+ # load issues
+ input.atwho 'load', "issues", data.issues
+ # load emojis
+ input.atwho 'load', ":", data.emojis
diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee
new file mode 100644
index 00000000000..c0ffccd8f70
--- /dev/null
+++ b/app/assets/javascripts/groups.js.coffee
@@ -0,0 +1,6 @@
+class GroupMembers
+ constructor: ->
+ $('li.users_group').bind 'ajax:success', ->
+ $(this).fadeOut()
+
+@GroupMembers = GroupMembers
diff --git a/app/assets/javascripts/issues.js b/app/assets/javascripts/issues.js
deleted file mode 100644
index 9ba1a3f1bba..00000000000
--- a/app/assets/javascripts/issues.js
+++ /dev/null
@@ -1,80 +0,0 @@
-function initIssuesSearch() {
- var href = $('#issue_search_form').attr('action');
- var last_terms = '';
-
- $('#issue_search').keyup(function() {
- var terms = $(this).val();
- var milestone_id = $('#milestone_id').val();
- var status = $('#status').val();
-
- if (terms != last_terms) {
- last_terms = terms;
-
- if (terms.length >= 2 || terms.length == 0) {
- $.get(href, { 'status': status, 'terms': terms, 'milestone_id': milestone_id }, function(response) {
- $('#issues-table').html(response);
- });
- }
- }
- });
-}
-
-/**
- * Init issues page
- *
- */
-function issuesPage(){
- initIssuesSearch();
- $("#update_status").chosen();
- $("#update_assignee_id").chosen();
- $("#update_milestone_id").chosen();
-
- $("#label_name").chosen();
- $("#assignee_id").chosen();
- $("#milestone_id").chosen();
- $("#milestone_id, #assignee_id, #label_name").on("change", function(){
- $(this).closest("form").submit();
- });
-
- $('body').on('ajax:success', '.close_issue, .reopen_issue', function(){
- var t = $(this),
- totalIssues,
- reopen = t.hasClass('reopen_issue');
- $('.issue_counter').each(function(){
- var issue = $(this);
- totalIssues = parseInt( $(this).html(), 10 );
-
- if( reopen && issue.closest('.main_menu').length ){
- $(this).html( totalIssues+1 );
- }else {
- $(this).html( totalIssues-1 );
- }
- });
-
- });
-
- $(".check_all_issues").click(function () {
- $('.selected_issue').attr('checked', this.checked);
- issuesCheckChanged();
- });
-
- $('.selected_issue').bind('change', issuesCheckChanged);
-}
-
-function issuesCheckChanged() {
- var checked_issues = $('.selected_issue:checked');
-
- if(checked_issues.length > 0) {
- var ids = []
- $.each(checked_issues, function(index, value) {
- ids.push($(value).attr("data-id"));
- })
- $('#update_issues_ids').val(ids);
- $('.issues_filters').hide();
- $('.issues_bulk_update').show();
- } else {
- $('#update_issues_ids').val([]);
- $('.issues_bulk_update').hide();
- $('.issues_filters').show();
- }
-}
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
new file mode 100644
index 00000000000..67d9498c50a
--- /dev/null
+++ b/app/assets/javascripts/issues.js.coffee
@@ -0,0 +1,72 @@
+@Issues =
+ init: ->
+ Issues.initSearch()
+ Issues.initSelects()
+ Issues.initChecks()
+
+ $("body").on "ajax:success", ".close_issue, .reopen_issue", ->
+ t = $(this)
+ totalIssues = undefined
+ reopen = t.hasClass("reopen_issue")
+ $(".issue_counter").each ->
+ issue = $(this)
+ totalIssues = parseInt($(this).html(), 10)
+ if reopen and issue.closest(".main_menu").length
+ $(this).html totalIssues + 1
+ else
+ $(this).html totalIssues - 1
+ $("body").on "click", ".issues-filters .dropdown-menu a", ->
+ $('.issues-list').block(
+ message: null,
+ overlayCSS:
+ backgroundColor: '#DDD'
+ opacity: .4
+ )
+
+ reload: ->
+ Issues.initSelects()
+ Issues.initChecks()
+ $('#filter_issue_search').val($('#issue_search').val())
+
+ initSelects: ->
+ $("#update_status").chosen()
+ $("#update_assignee_id").chosen()
+ $("#update_milestone_id").chosen()
+ $("#label_name").chosen()
+ $("#assignee_id").chosen()
+ $("#milestone_id").chosen()
+ $("#milestone_id, #assignee_id, #label_name").on "change", ->
+ $(this).closest("form").submit()
+
+ initChecks: ->
+ $(".check_all_issues").click ->
+ $(".selected_issue").attr "checked", @checked
+ Issues.checkChanged()
+
+ $(".selected_issue").bind "change", Issues.checkChanged
+
+
+ initSearch: ->
+ form = $("#issue_search_form")
+ last_terms = ""
+ $("#issue_search").keyup ->
+ terms = $(this).val()
+ unless terms is last_terms
+ last_terms = terms
+ if terms.length >= 2 or terms.length is 0
+ form.submit()
+
+ checkChanged: ->
+ checked_issues = $(".selected_issue:checked")
+ if checked_issues.length > 0
+ ids = []
+ $.each checked_issues, (index, value) ->
+ ids.push $(value).attr("data-id")
+
+ $("#update_issues_ids").val ids
+ $(".issues-filters").hide()
+ $(".issues_bulk_update").show()
+ else
+ $("#update_issues_ids").val []
+ $(".issues_bulk_update").hide()
+ $(".issues-filters").show()
diff --git a/app/assets/javascripts/lib/jquery.timeago.js b/app/assets/javascripts/lib/jquery.timeago.js
new file mode 100644
index 00000000000..cc17aa7d3d1
--- /dev/null
+++ b/app/assets/javascripts/lib/jquery.timeago.js
@@ -0,0 +1,181 @@
+/**
+ * Timeago is a jQuery plugin that makes it easy to support automatically
+ * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
+ *
+ * @name timeago
+ * @version 1.1.0
+ * @requires jQuery v1.2.3+
+ * @author Ryan McGeary
+ * @license MIT License - http://www.opensource.org/licenses/mit-license.php
+ *
+ * For usage and examples, visit:
+ * http://timeago.yarp.com/
+ *
+ * Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
+ */
+
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], factory);
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+}(function ($) {
+ $.timeago = function(timestamp) {
+ if (timestamp instanceof Date) {
+ return inWords(timestamp);
+ } else if (typeof timestamp === "string") {
+ return inWords($.timeago.parse(timestamp));
+ } else if (typeof timestamp === "number") {
+ return inWords(new Date(timestamp));
+ } else {
+ return inWords($.timeago.datetime(timestamp));
+ }
+ };
+ var $t = $.timeago;
+
+ $.extend($.timeago, {
+ settings: {
+ refreshMillis: 60000,
+ allowFuture: false,
+ strings: {
+ prefixAgo: null,
+ prefixFromNow: null,
+ suffixAgo: "ago",
+ suffixFromNow: "from now",
+ seconds: "less than a minute",
+ minute: "about a minute",
+ minutes: "%d minutes",
+ hour: "about an hour",
+ hours: "about %d hours",
+ day: "a day",
+ days: "%d days",
+ month: "about a month",
+ months: "%d months",
+ year: "about a year",
+ years: "%d years",
+ wordSeparator: " ",
+ numbers: []
+ }
+ },
+ inWords: function(distanceMillis) {
+ var $l = this.settings.strings;
+ var prefix = $l.prefixAgo;
+ var suffix = $l.suffixAgo;
+ if (this.settings.allowFuture) {
+ if (distanceMillis < 0) {
+ prefix = $l.prefixFromNow;
+ suffix = $l.suffixFromNow;
+ }
+ }
+
+ var seconds = Math.abs(distanceMillis) / 1000;
+ var minutes = seconds / 60;
+ var hours = minutes / 60;
+ var days = hours / 24;
+ var years = days / 365;
+
+ function substitute(stringOrFunction, number) {
+ var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
+ var value = ($l.numbers && $l.numbers[number]) || number;
+ return string.replace(/%d/i, value);
+ }
+
+ var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
+ seconds < 90 && substitute($l.minute, 1) ||
+ minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
+ minutes < 90 && substitute($l.hour, 1) ||
+ hours < 24 && substitute($l.hours, Math.round(hours)) ||
+ hours < 42 && substitute($l.day, 1) ||
+ days < 30 && substitute($l.days, Math.round(days)) ||
+ days < 45 && substitute($l.month, 1) ||
+ days < 365 && substitute($l.months, Math.round(days / 30)) ||
+ years < 1.5 && substitute($l.year, 1) ||
+ substitute($l.years, Math.round(years));
+
+ var separator = $l.wordSeparator || "";
+ if ($l.wordSeparator === undefined) { separator = " "; }
+ return $.trim([prefix, words, suffix].join(separator));
+ },
+ parse: function(iso8601) {
+ var s = $.trim(iso8601);
+ s = s.replace(/\.\d+/,""); // remove milliseconds
+ s = s.replace(/-/,"/").replace(/-/,"/");
+ s = s.replace(/T/," ").replace(/Z/," UTC");
+ s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
+ return new Date(s);
+ },
+ datetime: function(elem) {
+ var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
+ return $t.parse(iso8601);
+ },
+ isTime: function(elem) {
+ // jQuery's `is()` doesn't play well with HTML5 in IE
+ return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
+ }
+ });
+
+ // functions that can be called via $(el).timeago('action')
+ // init is default when no action is given
+ // functions are called with context of a single element
+ var functions = {
+ init: function(){
+ var refresh_el = $.proxy(refresh, this);
+ refresh_el();
+ var $s = $t.settings;
+ if ($s.refreshMillis > 0) {
+ setInterval(refresh_el, $s.refreshMillis);
+ }
+ },
+ update: function(time){
+ $(this).data('timeago', { datetime: $t.parse(time) });
+ refresh.apply(this);
+ }
+ };
+
+ $.fn.timeago = function(action, options) {
+ var fn = action ? functions[action] : functions.init;
+ if(!fn){
+ throw new Error("Unknown function name '"+ action +"' for timeago");
+ }
+ // each over objects here and call the requested function
+ this.each(function(){
+ fn.call(this, options);
+ });
+ return this;
+ };
+
+ function refresh() {
+ var data = prepareData(this);
+ if (!isNaN(data.datetime)) {
+ $(this).text(inWords(data.datetime));
+ }
+ return this;
+ }
+
+ function prepareData(element) {
+ element = $(element);
+ if (!element.data("timeago")) {
+ element.data("timeago", { datetime: $t.datetime(element) });
+ var text = $.trim(element.text());
+ if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
+ element.attr("title", text);
+ }
+ }
+ return element.data("timeago");
+ }
+
+ function inWords(date) {
+ return $t.inWords(distance(date));
+ }
+
+ function distance(date) {
+ return (new Date().getTime() - date.getTime());
+ }
+
+ // fix for IE6 suckage
+ document.createElement("abbr");
+ document.createElement("time");
+}));
diff --git a/app/assets/javascripts/lib/md5.js b/app/assets/javascripts/lib/md5.js
new file mode 100644
index 00000000000..b63716eaad2
--- /dev/null
+++ b/app/assets/javascripts/lib/md5.js
@@ -0,0 +1,211 @@
+function md5 (str) {
+ // http://kevin.vanzonneveld.net
+ // + original by: Webtoolkit.info (http://www.webtoolkit.info/)
+ // + namespaced by: Michael White (http://getsprink.com)
+ // + tweaked by: Jack
+ // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + input by: Brett Zamir (http://brett-zamir.me)
+ // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // - depends on: utf8_encode
+ // * example 1: md5('Kevin van Zonneveld');
+ // * returns 1: '6e658d4bfcb59cc13f96c14450ac40b9'
+ var xl;
+
+ var rotateLeft = function (lValue, iShiftBits) {
+ return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
+ };
+
+ var addUnsigned = function (lX, lY) {
+ var lX4, lY4, lX8, lY8, lResult;
+ lX8 = (lX & 0x80000000);
+ lY8 = (lY & 0x80000000);
+ lX4 = (lX & 0x40000000);
+ lY4 = (lY & 0x40000000);
+ lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
+ if (lX4 & lY4) {
+ return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
+ }
+ if (lX4 | lY4) {
+ if (lResult & 0x40000000) {
+ return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
+ } else {
+ return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
+ }
+ } else {
+ return (lResult ^ lX8 ^ lY8);
+ }
+ };
+
+ var _F = function (x, y, z) {
+ return (x & y) | ((~x) & z);
+ };
+ var _G = function (x, y, z) {
+ return (x & z) | (y & (~z));
+ };
+ var _H = function (x, y, z) {
+ return (x ^ y ^ z);
+ };
+ var _I = function (x, y, z) {
+ return (y ^ (x | (~z)));
+ };
+
+ var _FF = function (a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(_F(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var _GG = function (a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(_G(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var _HH = function (a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(_H(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var _II = function (a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(_I(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var convertToWordArray = function (str) {
+ var lWordCount;
+ var lMessageLength = str.length;
+ var lNumberOfWords_temp1 = lMessageLength + 8;
+ var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
+ var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
+ var lWordArray = new Array(lNumberOfWords - 1);
+ var lBytePosition = 0;
+ var lByteCount = 0;
+ while (lByteCount < lMessageLength) {
+ lWordCount = (lByteCount - (lByteCount % 4)) / 4;
+ lBytePosition = (lByteCount % 4) * 8;
+ lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount) << lBytePosition));
+ lByteCount++;
+ }
+ lWordCount = (lByteCount - (lByteCount % 4)) / 4;
+ lBytePosition = (lByteCount % 4) * 8;
+ lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
+ lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
+ lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
+ return lWordArray;
+ };
+
+ var wordToHex = function (lValue) {
+ var wordToHexValue = "",
+ wordToHexValue_temp = "",
+ lByte, lCount;
+ for (lCount = 0; lCount <= 3; lCount++) {
+ lByte = (lValue >>> (lCount * 8)) & 255;
+ wordToHexValue_temp = "0" + lByte.toString(16);
+ wordToHexValue = wordToHexValue + wordToHexValue_temp.substr(wordToHexValue_temp.length - 2, 2);
+ }
+ return wordToHexValue;
+ };
+
+ var x = [],
+ k, AA, BB, CC, DD, a, b, c, d, S11 = 7,
+ S12 = 12,
+ S13 = 17,
+ S14 = 22,
+ S21 = 5,
+ S22 = 9,
+ S23 = 14,
+ S24 = 20,
+ S31 = 4,
+ S32 = 11,
+ S33 = 16,
+ S34 = 23,
+ S41 = 6,
+ S42 = 10,
+ S43 = 15,
+ S44 = 21;
+
+ str = this.utf8_encode(str);
+ x = convertToWordArray(str);
+ a = 0x67452301;
+ b = 0xEFCDAB89;
+ c = 0x98BADCFE;
+ d = 0x10325476;
+
+ xl = x.length;
+ for (k = 0; k < xl; k += 16) {
+ AA = a;
+ BB = b;
+ CC = c;
+ DD = d;
+ a = _FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
+ d = _FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
+ c = _FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
+ b = _FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
+ a = _FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
+ d = _FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
+ c = _FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
+ b = _FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
+ a = _FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
+ d = _FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
+ c = _FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
+ b = _FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
+ a = _FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
+ d = _FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
+ c = _FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
+ b = _FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
+ a = _GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
+ d = _GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
+ c = _GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
+ b = _GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
+ a = _GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
+ d = _GG(d, a, b, c, x[k + 10], S22, 0x2441453);
+ c = _GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
+ b = _GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
+ a = _GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
+ d = _GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
+ c = _GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
+ b = _GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
+ a = _GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
+ d = _GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
+ c = _GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
+ b = _GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
+ a = _HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
+ d = _HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
+ c = _HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
+ b = _HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
+ a = _HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
+ d = _HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
+ c = _HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
+ b = _HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
+ a = _HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
+ d = _HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
+ c = _HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
+ b = _HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
+ a = _HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
+ d = _HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
+ c = _HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
+ b = _HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
+ a = _II(a, b, c, d, x[k + 0], S41, 0xF4292244);
+ d = _II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
+ c = _II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
+ b = _II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
+ a = _II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
+ d = _II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
+ c = _II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
+ b = _II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
+ a = _II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
+ d = _II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
+ c = _II(c, d, a, b, x[k + 6], S43, 0xA3014314);
+ b = _II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
+ a = _II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
+ d = _II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
+ c = _II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
+ b = _II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
+ a = addUnsigned(a, AA);
+ b = addUnsigned(b, BB);
+ c = addUnsigned(c, CC);
+ d = addUnsigned(d, DD);
+ }
+
+ var temp = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
+
+ return temp.toLowerCase();
+}
diff --git a/app/assets/javascripts/lib/utf8_encode.js b/app/assets/javascripts/lib/utf8_encode.js
new file mode 100644
index 00000000000..39ffe44dae0
--- /dev/null
+++ b/app/assets/javascripts/lib/utf8_encode.js
@@ -0,0 +1,70 @@
+function utf8_encode (argString) {
+ // http://kevin.vanzonneveld.net
+ // + original by: Webtoolkit.info (http://www.webtoolkit.info/)
+ // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + improved by: sowberry
+ // + tweaked by: Jack
+ // + bugfixed by: Onno Marsman
+ // + improved by: Yves Sucaet
+ // + bugfixed by: Onno Marsman
+ // + bugfixed by: Ulrich
+ // + bugfixed by: Rafal Kukawski
+ // + improved by: kirilloid
+ // + bugfixed by: kirilloid
+ // * example 1: utf8_encode('Kevin van Zonneveld');
+ // * returns 1: 'Kevin van Zonneveld'
+
+ if (argString === null || typeof argString === "undefined") {
+ return "";
+ }
+
+ var string = (argString + ''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n");
+ var utftext = '',
+ start, end, stringl = 0;
+
+ start = end = 0;
+ stringl = string.length;
+ for (var n = 0; n < stringl; n++) {
+ var c1 = string.charCodeAt(n);
+ var enc = null;
+
+ if (c1 < 128) {
+ end++;
+ } else if (c1 > 127 && c1 < 2048) {
+ enc = String.fromCharCode(
+ (c1 >> 6) | 192,
+ ( c1 & 63) | 128
+ );
+ } else if (c1 & 0xF800 != 0xD800) {
+ enc = String.fromCharCode(
+ (c1 >> 12) | 224,
+ ((c1 >> 6) & 63) | 128,
+ ( c1 & 63) | 128
+ );
+ } else { // surrogate pairs
+ if (c1 & 0xFC00 != 0xD800) { throw new RangeError("Unmatched trail surrogate at " + n); }
+ var c2 = string.charCodeAt(++n);
+ if (c2 & 0xFC00 != 0xDC00) { throw new RangeError("Unmatched lead surrogate at " + (n-1)); }
+ c1 = ((c1 & 0x3FF) << 10) + (c2 & 0x3FF) + 0x10000;
+ enc = String.fromCharCode(
+ (c1 >> 18) | 240,
+ ((c1 >> 12) & 63) | 128,
+ ((c1 >> 6) & 63) | 128,
+ ( c1 & 63) | 128
+ );
+ }
+ if (enc !== null) {
+ if (end > start) {
+ utftext += string.slice(start, end);
+ }
+ utftext += enc;
+ start = end = n + 1;
+ }
+ }
+
+ if (end > start) {
+ utftext += string.slice(start, stringl);
+ }
+
+ return utftext;
+}
diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/main.js.coffee
index 5aaea50cf21..011244a5868 100644
--- a/app/assets/javascripts/main.js.coffee
+++ b/app/assets/javascripts/main.js.coffee
@@ -7,6 +7,8 @@ window.slugify = (text) ->
window.ajaxGet = (url) ->
$.ajax({type: "GET", url: url, dataType: "script"})
+window.showAndHide = (selector) ->
+
window.errorMessage = (message) ->
ehtml = $("<p>")
ehtml.addClass("error_message")
@@ -32,13 +34,41 @@ window.disableButtonIfEmptyField = (field_selector, button_selector) ->
else
closest_submit.enable()
+window.sanitize = (str) ->
+ return str.replace(/<(?:.|\n)*?>/gm, '')
+
+window.linkify = (str) ->
+ exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig
+ return str.replace(exp,"<a href='$1'>$1</a>")
+
+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:receive", stopSpinner)
+
$ ->
# Click a .one_click_select field, select the contents
$(".one_click_select").on 'click', -> $(@).select()
+ $('.remove-row').bind 'ajax:success', ->
+ $(this).closest('li').fadeOut()
+
# Click a .appear-link, appear-data fadeout
- $(".appear-link").on 'click', ->
+ $(".appear-link").on 'click', (e) ->
$('.appear-data').fadeIn()
+ e.preventDefault()
# Initialize chosen selects
$('select.chosen').chosen()
@@ -49,11 +79,17 @@ $ ->
# Bottom tooltip
$('.has_bottom_tooltip').tooltip(placement: 'bottom')
+ # Form submitter
+ $('.trigger-submit').on 'change', ->
+ $(@).parents('form').submit()
+
+ $("abbr.timeago").timeago()
+
# Flash
- if (flash = $("#flash-container")).length > 0
- flash.click -> $(@).slideUp("slow")
- flash.slideDown "slow"
- setTimeout (-> flash.slideUp("slow")), 3000
+ if (flash = $(".flash-container")).length > 0
+ flash.click -> $(@).fadeOut()
+ flash.show()
+ setTimeout (-> flash.fadeOut()), 5000
# Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
@@ -77,9 +113,13 @@ $ ->
when 115
$("#search").focus()
e.preventDefault()
+ when 63
+ new Shortcuts()
+ e.preventDefault()
+
# Commit show suppressed diff
- $(".supp_diff_link").bind "click", ->
+ $(".content").on "click", ".supp_diff_link", ->
$(@).next('table').show()
$(@).remove()
diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee
index 65ed817c7c6..5400bc5c1ad 100644
--- a/app/assets/javascripts/merge_requests.js.coffee
+++ b/app/assets/javascripts/merge_requests.js.coffee
@@ -11,7 +11,7 @@ class MergeRequest
constructor: (@opts) ->
this.$el = $('.merge-request')
- @diffs_loaded = false
+ @diffs_loaded = if @opts.action == 'diffs' then true else false
@commits_loaded = false
this.activateTab(@opts.action)
@@ -21,17 +21,19 @@ class MergeRequest
this.initMergeWidget()
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
+
+ modal = $('#modal_merge_info').modal(show: false)
# Local jQuery finder
$: (selector) ->
this.$el.find(selector)
initMergeWidget: ->
- this.showState( @opts.current_state )
+ this.showState( @opts.current_status )
if this.$('.automerge_widget').length and @opts.check_enable
$.get @opts.url_to_automerge_check, (data) =>
- this.showState( data.state )
+ this.showState( data.merge_status )
, 'json'
if @opts.ci_enable
@@ -76,7 +78,6 @@ class MergeRequest
$('.ci_widget.ci-' + state).show()
loadDiff: (event) ->
- $('.dashboard-loader').show()
$.ajax
type: 'GET'
url: this.$('.nav-tabs .diffs-tab a').attr('href')
diff --git a/app/assets/javascripts/milestones.js.coffee b/app/assets/javascripts/milestones.js.coffee
deleted file mode 100644
index 99a52bf4d3f..00000000000
--- a/app/assets/javascripts/milestones.js.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-$ ->
- $('.milestone-issue-filter li[data-closed]').addClass('hide')
-
- $('.milestone-issue-filter ul.nav li a').click ->
- $('.milestone-issue-filter li').toggleClass('active')
- $('.milestone-issue-filter li[data-closed]').toggleClass('hide')
- false
-
- $('.milestone-merge-requests-filter li[data-closed]').addClass('hide')
-
- $('.milestone-merge-requests-filter ul.nav li a').click ->
- $('.milestone-merge-requests-filter li').toggleClass('active')
- $('.milestone-merge-requests-filter li[data-closed]').toggleClass('hide')
- false
diff --git a/app/assets/javascripts/network.js.coffee b/app/assets/javascripts/network.js.coffee
new file mode 100644
index 00000000000..cea5986f45a
--- /dev/null
+++ b/app/assets/javascripts/network.js.coffee
@@ -0,0 +1,11 @@
+class Network
+ constructor: (opts) ->
+ $("#filter_ref").click ->
+ $(this).closest('form').submit()
+
+ branch_graph = new BranchGraph($(".network-graph"), opts)
+
+ vph = $(window).height() - 250
+ $('.network-graph').css 'height': (vph + 'px')
+
+@Network = Network
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 919c6b7f4a2..5225623c1f0 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1,37 +1,37 @@
var NoteList = {
-
+ id: null,
notes_path: null,
target_params: null,
target_id: 0,
target_type: null,
- top_id: 0,
- bottom_id: 0,
- loading_more_disabled: false,
- reversed: false,
init: function(tid, tt, path) {
NoteList.notes_path = path + ".js";
NoteList.target_id = tid;
NoteList.target_type = tt;
- NoteList.reversed = $("#notes-list").is(".reversed");
NoteList.target_params = "target_type=" + NoteList.target_type + "&target_id=" + NoteList.target_id;
NoteList.setupMainTargetNoteForm();
- if(NoteList.reversed) {
- var form = $(".js-main-target-form");
- form.find(".note-form-actions").hide();
- var textarea = form.find(".js-note-text");
- textarea.css("height", "40px");
- textarea.on("focus", function(){
- textarea.css("height", "80px");
- form.find(".note-form-actions").show();
- });
- }
-
// get initial set of notes
NoteList.getContent();
+ // Unbind events to prevent firing twice
+ $(document).off("click", ".js-add-diff-note-button");
+ $(document).off("click", ".js-discussion-reply-button");
+ $(document).off("click", ".js-note-preview-button");
+ $(document).off("click", ".js-note-attachment-input");
+ $(document).off("click", ".js-close-discussion-note-form");
+ $(document).off("click", ".js-note-delete");
+ $(document).off("click", ".js-note-edit");
+ $(document).off("click", ".js-note-edit-cancel");
+ $(document).off("click", ".js-note-attachment-delete");
+ $(document).off("click", ".js-choose-note-attachment-button");
+ $(document).off("click", ".js-show-outdated-discussion");
+
+ $(document).off("ajax:complete", ".js-main-target-form");
+
+
// add a new diff note
$(document).on("click",
".js-add-diff-note-button",
@@ -62,6 +62,26 @@ var NoteList = {
".js-note-delete",
NoteList.removeNote);
+ // show the edit note form
+ $(document).on("click",
+ ".js-note-edit",
+ NoteList.showEditNoteForm);
+
+ // cancel note editing
+ $(document).on("click",
+ ".note-edit-cancel",
+ NoteList.cancelNoteEdit);
+
+ // delete note attachment
+ $(document).on("click",
+ ".js-note-attachment-delete",
+ NoteList.deleteNoteAttachment);
+
+ // update the note after editing
+ $(document).on("ajax:complete",
+ "form.edit_note",
+ NoteList.updateNote);
+
// reset main target form after submit
$(document).on("ajax:complete",
".js-main-target-form",
@@ -69,12 +89,12 @@ var NoteList = {
$(document).on("click",
- ".js-choose-note-attachment-button",
- NoteList.chooseNoteAttachment);
+ ".js-choose-note-attachment-button",
+ NoteList.chooseNoteAttachment);
$(document).on("click",
- ".js-show-outdated-discussion",
- function(e) { $(this).next('.outdated-discussion').show(); e.preventDefault() });
+ ".js-show-outdated-discussion",
+ function(e) { $(this).next('.outdated-discussion').show(); e.preventDefault() });
},
@@ -113,8 +133,8 @@ var NoteList = {
/**
* Called when clicking the "Choose File" button.
- *
- * Opesn the file selection dialog.
+ *
+ * Opens the file selection dialog.
*/
chooseNoteAttachment: function() {
var form = $(this).closest("form");
@@ -149,7 +169,7 @@ var NoteList = {
/**
* Called in response to "cancel" on a diff note form.
- *
+ *
* Shows the reply button again.
* Removes the form and if necessary it's temporary row.
*/
@@ -157,7 +177,7 @@ var NoteList = {
var form = $(this).closest("form");
var row = form.closest("tr");
- // show the reply button (will only work for replys)
+ // show the reply button (will only work for replies)
form.prev(".js-discussion-reply-button").show();
if (row.is(".js-temp-notes-holder")) {
@@ -193,6 +213,60 @@ var NoteList = {
},
/**
+ * Called in response to clicking the edit note link
+ *
+ * Replaces the note text with the note edit form
+ * Adds a hidden div with the original content of the note to fill the edit note form with
+ * if the user cancels
+ */
+ showEditNoteForm: function(e) {
+ e.preventDefault();
+ var note = $(this).closest(".note");
+ note.find(".note-text").hide();
+
+ // Show the attachment delete link
+ note.find(".js-note-attachment-delete").show();
+
+ GitLab.GfmAutoComplete.setup();
+
+ var form = note.find(".note-edit-form");
+ form.show();
+
+ var textarea = form.find("textarea");
+ var p = $("<p></p>").text(textarea.val());
+ var hidden_div = $('<div class="note-original-content"></div>').append(p);
+ form.append(hidden_div);
+ hidden_div.hide();
+ textarea.focus();
+ },
+
+ /**
+ * Called in response to clicking the cancel button when editing a note
+ *
+ * Resets and hides the note editing form
+ */
+ cancelNoteEdit: function(e) {
+ e.preventDefault();
+ var note = $(this).closest(".note");
+ NoteList.resetNoteEditing(note);
+ },
+
+
+ /**
+ * Called in response to clicking the delete attachment link
+ *
+ * Removes the attachment wrapper view, including image tag if it exists
+ * Resets the note editing form
+ */
+ deleteNoteAttachment: function() {
+ var note = $(this).closest(".note");
+ note.find(".note-attachment").remove();
+ NoteList.resetNoteEditing(note);
+ NoteList.rewriteTimestamp(note.find(".note-last-update"));
+ },
+
+
+ /**
* Called when clicking on the "reply" button for a diff line.
*
* Shows the note form below the notes.
@@ -327,7 +401,7 @@ var NoteList = {
/**
- * Gets an inital set of notes.
+ * Gets an initial set of notes.
*/
getContent: function() {
$.ajax({
@@ -344,128 +418,11 @@ var NoteList = {
* Replaces the content of #notes-list with the given html.
*/
setContent: function(newNoteIds, html) {
- NoteList.top_id = newNoteIds.first();
- NoteList.bottom_id = newNoteIds.last();
$("#notes-list").html(html);
-
- // for the wall
- if (NoteList.reversed) {
- // init infinite scrolling
- NoteList.initLoadMore();
-
- // init getting new notes
- NoteList.initRefreshNew();
- }
- },
-
-
- /**
- * Handle loading more notes when scrolling to the bottom of the page.
- * The id of the last note in the list is in NoteList.bottom_id.
- *
- * Set up refreshing only new notes after all notes have been loaded.
- */
-
-
- /**
- * Initializes loading more notes when scrolling to the bottom of the page.
- */
- initLoadMore: function() {
- $(document).endlessScroll({
- bottomPixels: 400,
- fireDelay: 1000,
- fireOnce:true,
- ceaseFire: function() {
- return NoteList.loading_more_disabled;
- },
- callback: function(i) {
- NoteList.getMore();
- }
- });
- },
-
- /**
- * Gets an additional set of notes.
- */
- getMore: function() {
- // only load more notes if there are no "new" notes
- $('.loading').show();
- $.ajax({
- url: NoteList.notes_path,
- data: NoteList.target_params + "&loading_more=1&" + (NoteList.reversed ? "before_id" : "after_id") + "=" + NoteList.bottom_id,
- complete: function(){ $('.js-notes-busy').removeClass("loading")},
- beforeSend: function() { $('.js-notes-busy').addClass("loading") },
- dataType: "script"
- });
- },
-
- /**
- * Called in response to getMore().
- * Append notes to #notes-list.
- */
- appendMoreNotes: function(newNoteIds, html) {
- var lastNewNoteId = newNoteIds.last();
- if(lastNewNoteId != NoteList.bottom_id) {
- NoteList.bottom_id = lastNewNoteId;
- $("#notes-list").append(html);
- }
- },
-
- /**
- * Called in response to getMore().
- * Disables loading more notes when scrolling to the bottom of the page.
- */
- finishedLoadingMore: function() {
- NoteList.loading_more_disabled = true;
-
- // make sure we are up to date
- NoteList.updateVotes();
},
/**
- * Handle refreshing and adding of new notes.
- *
- * New notes are all notes that are created after the site has been loaded.
- * The "old" notes are in #notes-list the "new" ones will be in #new-notes-list.
- * The id of the last "old" note is in NoteList.bottom_id.
- */
-
-
- /**
- * Initializes getting new notes every n seconds.
- *
- * Note: only used on wall.
- */
- initRefreshNew: function() {
- setInterval("NoteList.getNew()", 10000);
- },
-
- /**
- * Gets the new set of notes.
- *
- * Note: only used on wall.
- */
- getNew: function() {
- $.ajax({
- url: NoteList.notes_path,
- data: NoteList.target_params + "&loading_new=1&after_id=" + (NoteList.reversed ? NoteList.top_id : NoteList.bottom_id),
- dataType: "script"
- });
- },
-
- /**
- * Called in response to getNew().
- * Replaces the content of #new-notes-list with the given html.
- *
- * Note: only used on wall.
- */
- replaceNewNotes: function(newNoteIds, html) {
- $("#new-notes-list").html(html);
- NoteList.updateVotes();
- },
-
- /**
* Adds a single common note to #notes-list.
*/
appendNewNote: function(id, html) {
@@ -498,15 +455,6 @@ var NoteList = {
},
/**
- * Adds a single wall note to #new-notes-list.
- *
- * Note: only used on wall.
- */
- appendNewWallNote: function(id, html) {
- $("#new-notes-list").prepend(html);
- },
-
- /**
* Called in response the main target form has been successfully submitted.
*
* Removes any errors.
@@ -568,5 +516,65 @@ var NoteList = {
votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes));
votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes));
}
+ },
+
+ /**
+ * Called in response to the edit note form being submitted
+ *
+ * Updates the current note field.
+ * Hides the edit note form
+ */
+ updateNote: function(e, xhr, settings) {
+ response = JSON.parse(xhr.responseText);
+ if (response.success) {
+ var note_li = $("#note_" + response.id);
+ var note_text = note_li.find(".note-text");
+ note_text.html(response.note).show();
+
+ var note_form = note_li.find(".note-edit-form");
+ note_form.hide();
+ note_form.find(".btn-save").enableButton();
+
+ // Update the "Edited at xxx label" on the note to show it's just been updated
+ NoteList.rewriteTimestamp(note_li.find(".note-last-update"));
+ }
+ },
+
+ /**
+ * Called in response to the 'cancel note' link clicked, or after deleting a note attachment
+ *
+ * Hides the edit note form and shows the note
+ * Resets the edit note form textarea with the original content of the note
+ */
+ resetNoteEditing: function(note) {
+ note.find(".note-text").show();
+
+ // Hide the attachment delete link
+ note.find(".js-note-attachment-delete").hide();
+
+ // Put the original content of the note back into the edit form textarea
+ var form = note.find(".note-edit-form");
+ var original_content = form.find(".note-original-content");
+ form.find("textarea").val(original_content.text());
+ original_content.remove();
+
+ note.find(".note-edit-form").hide();
+ },
+
+ /**
+ * Utility function to generate new timestamp text for a note
+ *
+ */
+ rewriteTimestamp: function(element) {
+ // Strip all newlines from the existing timestamp
+ var ts = element.text().replace(/\n/g, ' ').trim();
+
+ // If the timestamp already has '(Edited xxx ago)' text, remove it
+ ts = ts.replace(new RegExp("\\(Edited [A-Za-z0-9 ]+\\)$", "gi"), "");
+
+ // Append "(Edited just now)"
+ ts = (ts + " <small>(Edited just now)</small>");
+
+ element.html(ts);
}
};
diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee
index 5f606acdf9c..5bd11d273a7 100644
--- a/app/assets/javascripts/pager.js.coffee
+++ b/app/assets/javascripts/pager.js.coffee
@@ -30,6 +30,7 @@
@disable = true
initLoadMore: ->
+ $(document).unbind('scroll')
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index 42207a390b3..e7974611cbe 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -1,13 +1,9 @@
$ ->
$('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
- # Hide any previous submission feedback
- $('.edit_user .update-feedback').hide()
-
# Submit the form
$('.edit_user').submit()
- # Go up the hierarchy and show the corresponding submission feedback element
- $(@).closest('fieldset').find('.update-feedback').show('highlight', {color: '#DFF0D8'}, 500)
+ new Flash("Appearance settings saved", "notice")
$('.update-username form').on 'ajax:before', ->
$('.loading-gif').show()
@@ -15,6 +11,8 @@ $ ->
$(this).find('.update-failed').hide()
$('.update-username form').on 'ajax:complete', ->
- $(this).find('.save-btn').removeAttr('disabled')
- $(this).find('.save-btn').removeClass('disabled')
+ $(this).find('.btn-save').enableButton()
$(this).find('.loading-gif').hide()
+
+ $('.update-notifications').on 'ajax:complete', ->
+ $(this).find('.btn-save').enableButton()
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
new file mode 100644
index 00000000000..83236b34814
--- /dev/null
+++ b/app/assets/javascripts/project.js.coffee
@@ -0,0 +1,42 @@
+class Project
+ constructor: ->
+ $('.project-edit-container').on 'ajax:before', =>
+ $('.project-edit-container').hide()
+ $('.save-project-loader').show()
+
+ @initEvents()
+
+
+ initEvents: ->
+ disableButtonIfEmptyField '#project_name', '.project-submit'
+
+ $('#project_issues_enabled').change ->
+ if ($(this).is(':checked') == true)
+ $('#project_issues_tracker').removeAttr('disabled')
+ else
+ $('#project_issues_tracker').attr('disabled', 'disabled')
+
+ $('#project_issues_tracker').change()
+
+ $('#project_issues_tracker').change ->
+ if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
+ $('#project_issues_tracker_id').attr('disabled', 'disabled')
+ else
+ $('#project_issues_tracker_id').removeAttr('disabled')
+
+
+@Project = Project
+
+$ ->
+ # Git clone panel switcher
+ scope = $ '.project_clone_holder'
+ if scope.length > 0
+ $('a, button', scope).click ->
+ $('a, button', scope).removeClass 'active'
+ $(@).addClass 'active'
+ $('#project_clone', scope).val $(@).data 'clone'
+ $(".clone").text("").append 'git remote add origin ' + $(@).data 'clone'
+
+ # Ref switcher
+ $('.project-refs-select').on 'change', ->
+ $(@).parents('form').submit()
diff --git a/app/assets/javascripts/project_import.js.coffee b/app/assets/javascripts/project_import.js.coffee
new file mode 100644
index 00000000000..7cf44da99fe
--- /dev/null
+++ b/app/assets/javascripts/project_import.js.coffee
@@ -0,0 +1,7 @@
+class ProjectImport
+ constructor: ->
+ setTimeout ->
+ Turbolinks.visit(location.href)
+ , 5000
+
+@ProjectImport = ProjectImport
diff --git a/app/assets/javascripts/projects.js.coffee b/app/assets/javascripts/projects.js.coffee
deleted file mode 100644
index d03a487c453..00000000000
--- a/app/assets/javascripts/projects.js.coffee
+++ /dev/null
@@ -1,20 +0,0 @@
-window.Projects = ->
- $('.new_project, .edit_project').on 'ajax:before', ->
- $('.project_new_holder, .project_edit_holder').hide()
- $('.save-project-loader').show()
-
- $('form #project_default_branch').chosen()
- disableButtonIfEmptyField '#project_name', '.project-submit'
-
-$ ->
- # Git clone panel switcher
- scope = $ '.project_clone_holder'
- if scope.length > 0
- $('a, button', scope).click ->
- $('a, button', scope).removeClass 'active'
- $(@).addClass 'active'
- $('#project_clone', scope).val $(@).data 'clone'
-
- # Ref switcher
- $('.project-refs-select').on 'change', ->
- $(@).parents('form').submit()
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
new file mode 100644
index 00000000000..3418690e109
--- /dev/null
+++ b/app/assets/javascripts/search_autocomplete.js.coffee
@@ -0,0 +1,8 @@
+class SearchAutocomplete
+ constructor: (json) ->
+ $("#search").autocomplete
+ source: json
+ select: (event, ui) ->
+ location.href = ui.item.url
+
+@SearchAutocomplete = SearchAutocomplete
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
new file mode 100644
index 00000000000..e7e40a066ec
--- /dev/null
+++ b/app/assets/javascripts/shortcuts.js.coffee
@@ -0,0 +1,11 @@
+class Shortcuts
+ constructor: ->
+ if $('#modal-shortcuts').length > 0
+ $('#modal-shortcuts').modal('show')
+ else
+ $.ajax(
+ url: '/help/shortcuts',
+ dataType: "script"
+ )
+
+@Shortcuts = Shortcuts
diff --git a/app/assets/javascripts/stat_graph.js.coffee b/app/assets/javascripts/stat_graph.js.coffee
new file mode 100644
index 00000000000..b129619696f
--- /dev/null
+++ b/app/assets/javascripts/stat_graph.js.coffee
@@ -0,0 +1,6 @@
+class window.StatGraph
+ @log: {}
+ @get_log: ->
+ @log
+ @set_log: (data) ->
+ @log = data
diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/stat_graph_contributors.js.coffee
new file mode 100644
index 00000000000..168b7337041
--- /dev/null
+++ b/app/assets/javascripts/stat_graph_contributors.js.coffee
@@ -0,0 +1,84 @@
+class window.ContributorsStatGraph
+ init: (log) ->
+ @parsed_log = ContributorsStatGraphUtil.parse_log(log)
+ @set_current_field("commits")
+ total_commits = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
+ author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field)
+ @add_master_graph(total_commits)
+ @add_authors_graph(author_commits)
+ @change_date_header()
+ add_master_graph: (total_data) ->
+ @master_graph = new ContributorsMasterGraph(total_data)
+ @master_graph.draw()
+ add_authors_graph: (author_data) ->
+ @authors = []
+ limited_author_data = author_data.slice(0, 100)
+ _.each(limited_author_data, (d) =>
+ author_header = @create_author_header(d)
+ $(".contributors-list").append(author_header)
+ @authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates)
+ author_graph.draw()
+ )
+ format_author_commit_info: (author) ->
+ commits = $('<span/>', {
+ class: 'graph-author-commits-count'
+ })
+ commits.text(author.commits + " commits")
+
+ additions = $('<span/>', {
+ class: 'graph-additions'
+ })
+ additions.text(author.additions + " ++")
+
+ deletions = $('<span/>', {
+ class: 'graph-deletions'
+ })
+ deletions.text(author.deletions + " --")
+
+ $('<span/>').append(commits)
+ .append(" / ")
+ .append(additions)
+ .append(" / ")
+ .append(deletions)
+
+ create_author_header: (author) ->
+ list_item = $('<li/>', {
+ class: 'person'
+ style: 'display: block;'
+ })
+ author_name = $('<h4>' + author.author_name + '</h4>')
+ author_email = $('<p class="graph-author-email">' + author.author_email + '</p>')
+ author_commit_info_span = $('<span/>', {
+ class: 'commits'
+ })
+ author_commit_info = @format_author_commit_info(author)
+ author_commit_info_span.html(author_commit_info)
+ list_item.append(author_name)
+ list_item.append(author_email)
+ list_item.append(author_commit_info_span)
+ list_item
+ redraw_master: ->
+ total_data = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
+ @master_graph.set_data(total_data)
+ @master_graph.redraw()
+ redraw_authors: ->
+ $("ol").html("")
+ x_domain = ContributorsGraph.prototype.x_domain
+ author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field, x_domain)
+ _.each(author_commits, (d) =>
+ @redraw_author_commit_info(d)
+ $(@authors[d.author_name].list_item).appendTo("ol")
+ @authors[d.author_name].set_data(d.dates)
+ @authors[d.author_name].redraw()
+ )
+ set_current_field: (field) ->
+ @field = field
+ change_date_header: ->
+ x_domain = ContributorsGraph.prototype.x_domain
+ print_date_format = d3.time.format("%B %e %Y")
+ print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1])
+ $("#date_header").text(print)
+ redraw_author_commit_info: (author) ->
+ author_list_item = $(@authors[author.author_name].list_item)
+ author_commit_info = @format_author_commit_info(author)
+ author_list_item.find("span").html(author_commit_info)
diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
new file mode 100644
index 00000000000..48443644169
--- /dev/null
+++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
@@ -0,0 +1,176 @@
+class window.ContributorsGraph
+ MARGIN:
+ top: 20
+ right: 20
+ bottom: 30
+ left: 50
+ x_domain: null
+ y_domain: null
+ dates: []
+ @set_x_domain: (data) =>
+ @prototype.x_domain = data
+ @set_y_domain: (data) =>
+ @prototype.y_domain = [0, d3.max(data, (d) ->
+ d.commits = d.commits ? d.additions ? d.deletions
+ )]
+ @init_x_domain: (data) =>
+ @prototype.x_domain = d3.extent(data, (d) ->
+ d.date
+ )
+ @init_y_domain: (data) =>
+ @prototype.y_domain = [0, d3.max(data, (d) ->
+ d.commits = d.commits ? d.additions ? d.deletions
+ )]
+ @init_domain: (data) =>
+ @init_x_domain(data)
+ @init_y_domain(data)
+ @set_dates: (data) =>
+ @prototype.dates = data
+ set_x_domain: ->
+ @x.domain(@x_domain)
+ set_y_domain: ->
+ @y.domain(@y_domain)
+ set_domain: ->
+ @set_x_domain()
+ @set_y_domain()
+ create_scale: (width, height) ->
+ @x = d3.time.scale().range([0, width]).clamp(true)
+ @y = d3.scale.linear().range([height, 0]).nice()
+ draw_x_axis: ->
+ @svg.append("g").attr("class", "x axis").attr("transform", "translate(0, #{@height})")
+ .call(@x_axis)
+ draw_y_axis: ->
+ @svg.append("g").attr("class", "y axis").call(@y_axis)
+ set_data: (data) ->
+ @data = data
+
+class window.ContributorsMasterGraph extends ContributorsGraph
+ constructor: (@data) ->
+ if $(window).width() > 1214
+ @width = 1100
+ else
+ @width = 870
+
+ @height = 200
+ @x = null
+ @y = null
+ @x_axis = null
+ @y_axis = null
+ @area = null
+ @svg = null
+ @brush = null
+ @x_max_domain = null
+ process_dates: (data) ->
+ dates = @get_dates(data)
+ @parse_dates(data)
+ ContributorsGraph.set_dates(dates)
+ get_dates: (data) ->
+ _.pluck(data, 'date')
+ parse_dates: (data) ->
+ parseDate = d3.time.format("%Y-%m-%d").parse
+ data.forEach((d) ->
+ d.date = parseDate(d.date)
+ )
+ create_scale: ->
+ super @width, @height
+ create_axes: ->
+ @x_axis = d3.svg.axis().scale(@x).orient("bottom")
+ @y_axis = d3.svg.axis().scale(@y).orient("left").ticks(5)
+ create_svg: ->
+ @svg = d3.select("#contributors-master").append("svg")
+ .attr("width", @width + @MARGIN.left + @MARGIN.right)
+ .attr("height", @height + @MARGIN.top + @MARGIN.bottom)
+ .attr("class", "tint-box")
+ .append("g")
+ .attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
+ create_area: (x, y) ->
+ @area = d3.svg.area().x((d) ->
+ x(d.date)
+ ).y0(@height).y1((d) ->
+ xa = d.commits = d.commits ? d.additions ? d.deletions
+ console.log(xa)
+ y(xa)
+ ).interpolate("basis")
+ create_brush: ->
+ @brush = d3.svg.brush().x(@x).on("brushend", @update_content)
+ draw_path: (data) ->
+ @svg.append("path").datum(data).attr("class", "area").attr("d", @area)
+ add_brush: ->
+ @svg.append("g").attr("class", "selection").call(@brush).selectAll("rect").attr("height", @height)
+ update_content: =>
+ ContributorsGraph.set_x_domain(if @brush.empty() then @x_max_domain else @brush.extent())
+ $("#brush_change").trigger('change')
+ draw: ->
+ @process_dates(@data)
+ @create_scale()
+ @create_axes()
+ ContributorsGraph.init_domain(@data)
+ @x_max_domain = @x_domain
+ @set_domain()
+ @create_area(@x, @y)
+ @create_svg()
+ @create_brush()
+ @draw_path(@data)
+ @draw_x_axis()
+ @draw_y_axis()
+ @add_brush()
+ redraw: ->
+ @process_dates(@data)
+ ContributorsGraph.set_y_domain(@data)
+ @set_y_domain()
+ @svg.select("path").datum(@data)
+ @svg.select("path").attr("d", @area)
+ @svg.select(".y.axis").call(@y_axis)
+
+class window.ContributorsAuthorGraph extends ContributorsGraph
+ constructor: (@data) ->
+ if $(window).width() > 1214
+ @width = 490
+ else
+ @width = 380
+
+ @height = 200
+ @x = null
+ @y = null
+ @x_axis = null
+ @y_axis = null
+ @area = null
+ @svg = null
+ @list_item = null
+ create_scale: ->
+ super @width, @height
+ create_axes: ->
+ @x_axis = d3.svg.axis().scale(@x).orient("bottom").ticks(8)
+ @y_axis = d3.svg.axis().scale(@y).orient("left").ticks(5)
+ create_area: (x, y) ->
+ @area = d3.svg.area().x((d) ->
+ parseDate = d3.time.format("%Y-%m-%d").parse
+ x(parseDate(d))
+ ).y0(@height).y1((d) =>
+ if @data[d]? then y(@data[d]) else y(0)
+ ).interpolate("basis")
+ create_svg: ->
+ @list_item = d3.selectAll(".person")[0].pop()
+ @svg = d3.select(@list_item).append("svg")
+ .attr("width", @width + @MARGIN.left + @MARGIN.right)
+ .attr("height", @height + @MARGIN.top + @MARGIN.bottom)
+ .attr("class", "spark")
+ .append("g")
+ .attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
+ draw_path: (data) ->
+ @svg.append("path").datum(data).attr("class", "area-contributor").attr("d", @area)
+ draw: ->
+ @create_scale()
+ @create_axes()
+ @set_domain()
+ @create_area(@x, @y)
+ @create_svg()
+ @draw_path(@dates)
+ @draw_x_axis()
+ @draw_y_axis()
+ redraw: ->
+ @set_domain()
+ @svg.select("path").datum(@dates)
+ @svg.select("path").attr("d", @area)
+ @svg.select(".x.axis").call(@x_axis)
+ @svg.select(".y.axis").call(@y_axis)
diff --git a/app/assets/javascripts/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
new file mode 100644
index 00000000000..364cab18377
--- /dev/null
+++ b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
@@ -0,0 +1,93 @@
+window.ContributorsStatGraphUtil =
+ parse_log: (log) ->
+ total = {}
+ by_author = {}
+ for entry in log
+ @add_date(entry.date, total) unless total[entry.date]?
+ @add_author(entry, by_author) unless by_author[entry.author_name]?
+ @add_date(entry.date, by_author[entry.author_name]) unless by_author[entry.author_name][entry.date]
+ @store_data(entry, total[entry.date], by_author[entry.author_name][entry.date])
+ total = _.toArray(total)
+ by_author = _.toArray(by_author)
+ total: total, by_author: by_author
+
+ add_date: (date, collection) ->
+ collection[date] = {}
+ collection[date].date = date
+
+ add_author: (author, by_author) ->
+ by_author[author.author_name] = {}
+ by_author[author.author_name].author_name = author.author_name
+ by_author[author.author_name].author_email = author.author_email
+
+ store_data: (entry, total, by_author) ->
+ @store_commits(total, by_author)
+ @store_additions(entry, total, by_author)
+ @store_deletions(entry, total, by_author)
+
+ store_commits: (total, by_author) ->
+ @add(total, "commits", 1)
+ @add(by_author, "commits", 1)
+
+ add: (collection, field, value) ->
+ collection[field] ?= 0
+ collection[field] += value
+
+ store_additions: (entry, total, by_author) ->
+ entry.additions ?= 0
+ @add(total, "additions", entry.additions)
+ @add(by_author, "additions", entry.additions)
+
+ store_deletions: (entry, total, by_author) ->
+ entry.deletions ?= 0
+ @add(total, "deletions", entry.deletions)
+ @add(by_author, "deletions", entry.deletions)
+
+ get_total_data: (parsed_log, field) ->
+ log = parsed_log.total
+ total_data = @pick_field(log, field)
+ _.sortBy(total_data, (d) ->
+ d.date
+ )
+ pick_field: (log, field) ->
+ total_data = []
+ _.each(log, (d) ->
+ total_data.push(_.pick(d, [field, 'date']))
+ )
+ total_data
+
+ get_author_data: (parsed_log, field, date_range = null) ->
+ log = parsed_log.by_author
+ author_data = []
+
+ _.each(log, (log_entry) =>
+ parsed_log_entry = @parse_log_entry(log_entry, field, date_range)
+ if not _.isEmpty(parsed_log_entry.dates)
+ author_data.push(parsed_log_entry)
+ )
+
+ _.sortBy(author_data, (d) ->
+ d[field]
+ ).reverse()
+
+ parse_log_entry: (log_entry, field, date_range) ->
+ parsed_entry = {}
+ parsed_entry.author_name = log_entry.author_name
+ parsed_entry.author_email = log_entry.author_email
+ parsed_entry.dates = {}
+ parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0
+ _.each(_.omit(log_entry, 'author_name', 'author_email'), (value, key) =>
+ if @in_range(value.date, date_range)
+ parsed_entry.dates[value.date] = value[field]
+ parsed_entry.commits += value.commits
+ parsed_entry.additions += value.additions
+ parsed_entry.deletions += value.deletions
+ )
+ return parsed_entry
+
+ in_range: (date, date_range) ->
+ if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
+ true
+ else
+ false
+
diff --git a/app/assets/javascripts/team_members.js.coffee b/app/assets/javascripts/team_members.js.coffee
new file mode 100644
index 00000000000..5eaa8ad4ff9
--- /dev/null
+++ b/app/assets/javascripts/team_members.js.coffee
@@ -0,0 +1,6 @@
+class TeamMembers
+ constructor: ->
+ $('.team-members .project-access-select').on "change", ->
+ $(this.form).submit()
+
+@TeamMembers = TeamMembers
diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee
index 5003f9b00c7..4852e879b68 100644
--- a/app/assets/javascripts/tree.js.coffee
+++ b/app/assets/javascripts/tree.js.coffee
@@ -1,56 +1,43 @@
-# Code browser tree slider
-
-$ ->
- if $('#tree-slider').length > 0
- # Show the "Loading commit data" for only the first element
- $('span.log_loading:first').removeClass('hide')
-
- $('#tree-slider .tree-item-file-name a, .breadcrumb li > a').live "click", ->
- $("#tree-content-holder").hide("slide", { direction: "left" }, 150)
+class TreeView
+ constructor: ->
+ @initKeyNav()
+ # Code browser tree slider
# Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
- $("#tree-slider .tree-item").live 'click', (e) ->
- $('.tree-item-file-name a', this).trigger('click') if (e.target.nodeName != "A")
-
- # Show/Hide the loading spinner
- $('#tree-slider .tree-item-file-name a, .breadcrumb a, .project-refs-form').live
- "ajax:beforeSend": -> $('.tree_progress').addClass("loading")
- "ajax:complete": -> $('.tree_progress').removeClass("loading")
-
- # Maintain forward/back history while browsing the file tree
- ((window) ->
- History = window.History
- $ = window.jQuery
- document = window.document
+ $(".tree-content-holder .tree-item").on 'click', (e) ->
+ if (e.target.nodeName != "A")
+ path = $('.tree-item-file-name a', this).attr('href')
+ Turbolinks.visit(path)
- # Check to see if History.js is enabled for our Browser
- unless History.enabled
- return false
-
- $('#tree-slider .tree-item-file-name a, .breadcrumb li > a').live 'click', (e) ->
- History.pushState(null, null, decodeURIComponent($(@).attr('href')))
- return false
-
- History.Adapter.bind window, 'statechange', ->
- state = History.getState()
- window.ajaxGet(state.url)
- )(window)
-
- # See if there are lines selected
- # "#L12" and "#L34-56" supported
- highlightBlobLines = ->
- if window.location.hash isnt ""
- matches = window.location.hash.match(/\#L(\d+)(\-(\d+))?/)
- first_line = parseInt(matches?[1])
- last_line = parseInt(matches?[3])
-
- unless isNaN first_line
- last_line = first_line if isNaN(last_line)
- $("#tree-content-holder .highlight .line").removeClass("hll")
- $("#LC#{line}").addClass("hll") for line in [first_line..last_line]
- $("#L#{first_line}").ScrollTo()
+ # Show the "Loading commit data" for only the first element
+ $('span.log_loading:first').removeClass('hide')
- # Highlight the correct lines on load
- highlightBlobLines()
- # Highlight the correct lines when the hash part of the URL changes
- $(window).on 'hashchange', highlightBlobLines
+ initKeyNav: ->
+ li = $("tr.tree-item")
+ liSelected = null
+ $('body').keydown (e) ->
+ if e.which is 40
+ if liSelected
+ next = liSelected.next()
+ if next.length > 0
+ liSelected.removeClass "selected"
+ liSelected = next.addClass("selected")
+ else
+ liSelected = li.eq(0).addClass("selected")
+
+ $(liSelected).focus()
+ else if e.which is 38
+ if liSelected
+ next = liSelected.prev()
+ if next.length > 0
+ liSelected.removeClass "selected"
+ liSelected = next.addClass("selected")
+ else
+ liSelected = li.last().addClass("selected")
+
+ $(liSelected).focus()
+ else if e.which is 13
+ path = $('.tree-item.selected .tree-item-file-name a').attr('href')
+ Turbolinks.visit(path)
+
+@TreeView = TreeView
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
new file mode 100644
index 00000000000..8286ca2f0c1
--- /dev/null
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -0,0 +1,37 @@
+$ ->
+ userFormatResult = (user) ->
+ avatar = gon.gravatar_url
+ avatar = avatar.replace('%{hash}', md5(user.email))
+ avatar = avatar.replace('%{size}', '24')
+
+ markup = "<div class='user-result'>"
+ markup += "<div class='user-image'><img class='avatar s24' src='" + avatar + "'></div>"
+ markup += "<div class='user-name'>" + user.name + "</div>"
+ markup += "<div class='user-username'>" + user.username + "</div>"
+ markup += "</div>"
+ markup
+
+ userFormatSelection = (user) ->
+ user.name
+
+ $('.ajax-users-select').each (i, select) ->
+ $(select).select2
+ placeholder: "Search for a user"
+ multiple: $(select).hasClass('multiselect')
+ minimumInputLength: 0
+ query: (query) ->
+ Api.users query.term, (users) ->
+ data = { results: users }
+ query.callback(data)
+
+ initSelection: (element, callback) ->
+ id = $(element).val()
+ if id isnt ""
+ Api.user(id, callback)
+
+
+ formatResult: userFormatResult
+ formatSelection: userFormatSelection
+ dropdownCssClass: "ajax-users-dropdown"
+ escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
+ m
diff --git a/app/assets/javascripts/wall.js.coffee b/app/assets/javascripts/wall.js.coffee
new file mode 100644
index 00000000000..4cc11331aca
--- /dev/null
+++ b/app/assets/javascripts/wall.js.coffee
@@ -0,0 +1,85 @@
+class Wall
+ constructor: (project_id) ->
+ @project_id = project_id
+ @note_ids = []
+ @getContent()
+ @initRefresh()
+ @initForm()
+
+ #
+ # Gets an initial set of notes.
+ #
+ getContent: ->
+ Api.notes @project_id, (notes) =>
+ $.each notes, (i, note) =>
+ # render note if it not present in loaded list
+ # or skip if rendered
+ if $.inArray(note.id, @note_ids) == -1
+ @note_ids.push(note.id)
+ @renderNote(note)
+ @scrollDown()
+ $("abbr.timeago").timeago()
+
+ initRefresh: ->
+ setInterval =>
+ @refresh()
+ , 10000
+
+ refresh: ->
+ @getContent()
+
+ scrollDown: ->
+ notes = $('ul.notes')
+ $('body, html').scrollTop(notes.height())
+
+ initForm: ->
+ form = $('.wall-note-form')
+ form.find("#target_type").val('wall')
+
+ form.on 'ajax:success', =>
+ @refresh()
+ form.find(".js-note-text").val("").trigger("input")
+
+ form.on 'ajax:complete', ->
+ form.find(".js-comment-button").removeAttr('disabled')
+ form.find(".js-comment-button").removeClass('disabled')
+
+ form.on "click", ".js-choose-note-attachment-button", ->
+ form.find(".js-note-attachment-input").click()
+
+ form.on "change", ".js-note-attachment-input", ->
+ filename = $(this).val().replace(/^.*[\\\/]/, '')
+ form.find(".js-attachment-filename").text(filename)
+
+ form.find('.note_text').keydown (e) ->
+ if e.ctrlKey && e.keyCode == 13
+ form.find('.js-comment-button').submit()
+
+ form.show()
+
+ renderNote: (note) ->
+ template = @noteTemplate()
+ template = template.replace('{{author_name}}', note.author.name)
+ template = template.replace(/{{created_at}}/g, note.created_at)
+ template = template.replace('{{text}}', simpleFormat(note.body))
+
+ if note.attachment
+ file = '<i class="icon-paper-clip"/><a href="' + gon.relative_url_root + '/files/note/' + note.id + '/' + note.attachment + '">' + note.attachment + '</a>'
+ else
+ file = ''
+ template = template.replace('{{file}}', file)
+
+
+ $('ul.notes').append(template)
+
+ noteTemplate: ->
+ return '<li>
+ <strong class="wall-author">{{author_name}}</strong>
+ <span class="wall-text">
+ {{text}}
+ <span class="wall-file">{{file}}</span>
+ </span>
+ <abbr class="timeago" title="{{created_at}}">{{created_at}}</abbr>
+ </li>'
+
+@Wall = Wall
diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee
new file mode 100644
index 00000000000..17e790e5b7c
--- /dev/null
+++ b/app/assets/javascripts/wikis.js.coffee
@@ -0,0 +1,12 @@
+class Wikis
+ constructor: ->
+ $('.build-new-wiki').bind "click", ->
+ field = $('#new_wiki_path')
+ slug = field.val()
+ path = field.attr('data-wikis-path')
+
+ if(slug.length > 0)
+ location.href = path + "/" + slug
+
+
+@Wikis = Wikis
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 6b500b88823..b1a23427add 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -5,6 +5,7 @@
*= require jquery.ui.gitlab
*= require jquery.atwho
*= require chosen
+ *= require select2
*= require_self
*/
@@ -14,7 +15,7 @@
@import "gitlab_bootstrap.scss";
@import "common.scss";
-@import "ref_select.scss";
+@import "selects.scss";
@import "sections/header.scss";
@import "sections/nav.scss";
@@ -33,9 +34,15 @@
@import "sections/login.scss";
@import "sections/editor.scss";
@import "sections/admin.scss";
+@import "sections/wiki.scss";
+@import "sections/wall.scss";
+@import "sections/dashboard.scss";
+@import "sections/stat_graph.scss";
@import "highlight/white.scss";
@import "highlight/dark.scss";
+@import "highlight/solarized_dark.scss";
+@import "highlight/monokai.scss";
/**
* UI themes:
diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss
index 7ac8c2dd91c..1572227ec3a 100644
--- a/app/assets/stylesheets/common.scss
+++ b/app/assets/stylesheets/common.scss
@@ -17,33 +17,14 @@ body {
margin: 0 0;
}
-.visible_link,
.author_link {
color: $link_color;
}
.help li { color:$style_color; }
-.back_link {
- text-decoration: underline;
+.back-link {
font-size: 14px;
- font-weight: bold;
- padding: 10px 0;
- padding-bottom: 0;
-}
-
-.info_link {
- margin-right: 5px;
- float: left;
-
- img {
- width: 20px;
- }
-}
-
-.download_repo_link {
- background: url("images.png") no-repeat 0 -48px;
- padding-left: 20px;
}
table a code {
@@ -52,10 +33,6 @@ table a code {
margin-right: 3px;
}
-.span12 hr{
- margin-top: 5px;
-}
-
.loading {
margin: 20px auto;
background: url(ajax_loader.gif) no-repeat center center;
@@ -67,32 +44,30 @@ table a code {
}
/** FLASH message **/
-#flash-container {
- height: 50px;
- position: fixed;
- z-index: 10001;
- top: 0px;
- width: 100%;
- margin-bottom: 15px;
- overflow: hidden;
- background: white;
+.flash-container {
+ display: none;
cursor: pointer;
- border-bottom: 1px solid #ccc;
+ margin: 0;
text-align: center;
- display: none;
+ color: #fff;
+ font-size: 14px;
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ opacity: 0.8;
+ z-index: 100;
- h4 {
- color: #666;
- font-size: 18px;
- line-height: 38px;
- padding-top: 5px;
- margin: 2px;
- font-weight: normal;
+ .flash-notice {
+ background: #49C;
+ padding: 10px;
+ text-shadow: 0 1px 1px #178;
}
-}
-.git_url_wrapper {
- margin-right:50px
+ .flash-alert {
+ background: #C67;
+ text-shadow: 0 1px 1px #945;
+ padding: 10px;
+ }
}
span.update-author {
@@ -106,66 +81,18 @@ span.update-author {
}
}
-.dashboard-loader {
- float: left;
- margin: 10px;
- display: none;
-}
.user-mention {
color: #2FA0BB;
font-weight: bold;
}
-.neib {
- margin-right: 10px;
-}
-
.label {
- padding: 0px 4px;
- font-size: 10px;
+ padding: 1px 4px;
+ font-size: 12px;
font-style: normal;
- background-color: $link_color;
-
- &.label-success {
- background-color: #8D8;
- color: #333;
- text-shadow: 0 1px 1px white;
- }
-
- &.label-error {
- background-color: #D88;
- color: #333;
- text-shadow: 0 1px 1px white;
- }
-}
-
-form {
- @extend .form-horizontal;
-
- .actions {
- @extend .form-actions;
- }
-
- .clearfix {
- @extend .control-group;
- }
-
- .input {
- @extend .controls;
- }
-
- label {
- @extend .control-label;
- }
- .xlarge {
- @extend .input-xlarge;
- }
- .xxlarge {
- @extend .input-xxlarge;
- }
+ font-weight: normal;
}
-
.field_with_errors {
display: inline;
}
@@ -179,55 +106,10 @@ ul.breadcrumb {
}
a {
- color: #474D57;
- font-weight: bold;
- font-size: 14px;
- }
-
- .arrow {
- background: url("images.png") no-repeat -85px -77px;
- width: 19px;
- height: 16px;
- float: left;
- position: relative;
- left: -10px;
- padding: 0;
- margin: 0;
- }
-}
-
-input[type=text] {
- &.large_text {
- padding: 6px;
font-size: 16px;
}
}
-input.git_clone_url {
- width: 325px;
-}
-
-.merge-request-form-holder {
- select {
- width: 300px;
- }
-}
-
-/** Issues **/
-#issue_assignee_id {
- width: 300px;
-}
-
-#new_issue_dialog textarea{
- height: 100px;
-}
-
-.project_list_url {
- width: 250px;
- background:#fff !important;
-}
-
-
.line_holder {
&:hover {
td {
@@ -236,47 +118,12 @@ input.git_clone_url {
}
}
-li.commit {
- .avatar {
- width: 24px;
- top:-5px;
- margin-right: 10px;
- margin-left: 10px;
- }
-
- code {
- padding: 2px 2px 0;
- margin-top: -2px;
- &:hover {
- color: black;
- border: 1px solid #ccc;
- }
- }
-}
p.time {
color: #999;
font-size: 90%;
margin: 30px 3px 3px 2px;
}
-
-.styled_image {
- border: 2px solid #ddd;
-}
-
-
-
-/* Fix for readme code (stopped it from being yellow) */
-.readme {
- pre {
- background: white !important;
-
- code {
- background: none !important;
- }
- }
-}
-
.search-holder {
label, input {
height: 30px;
@@ -303,23 +150,17 @@ p.time {
top: -5px;
@include border-radius(4px);
+ &.success {
+ background: #4A4;
+ color: #FFF;
+ }
+
&.error {
background: #DA4E49;
color: #FFF;
}
}
-.arrow{
- background: #E3E5EA;
- padding: 5px;
- margin-top: 5px;
- @include border-radius(5px);
- text-shadow: none;
- color: #999;
- line-height: 16px;
- font-weight: bold;
-}
-
.thin_area{
height: 150px;
}
@@ -357,38 +198,6 @@ li.note {
}
}
-.remember_me {
- text-align: left;
-
- input {
- margin: 0;
- }
-
- span {
- padding-left: 5px;
- }
-}
-
-
-
-/**
- * Admin area
- *
- */
-.admin_dash {
- .data {
- a {
- h1 {
- line-height: 48px;
- font-size: 48px;
- padding: 20px;
- text-align: center;
- font-weight: normal;
- }
- }
- }
-}
-
.rss-icon {
img {
width: 24px;
@@ -400,25 +209,12 @@ li.note {
}
}
-
-
-/* CHZN reset few styles */
-.chzn-container-single .chzn-single {
- background: #FFF;
- border: 1px solid #bbb;
- box-shadow: none;
-}
-.chzn-container-active .chzn-single {
- background: #fff;
-}
-
-
.supp_diff_link,
.show-all-commits {
cursor: pointer;
}
-.merge_request,
+.merge-request,
.issue {
&.today{
background: #EFE;
@@ -445,14 +241,17 @@ li.note {
}
}
-.error_message {
- @extend .cred;
- border-left: 4px solid #E99;
+.error-message {
padding: 10px;
- margin-bottom: 10px;
- background: #FEE;
+ background: #C67;
padding-left: 20px;
+ margin: 0;
+ color: #FFF;
+ a {
+ color: #fff;
+ text-decoration: underline;
+ }
&.centered {
text-align: center;
}
@@ -518,27 +317,6 @@ pre {
}
}
-.float-link {
- float: left;
- margin-right: 15px;
- .s16 {
- margin-right: 5px;
- }
-}
-
-.dashboard-search-filter {
- padding:5px;
-
- .search-text-input {
- float:left;
- @extend .span2;
- }
- .btn {
- margin-left: 5px;
- float:left;
- }
-}
-
h1.http_status_code {
font-size: 56px;
line-height: 100px;
@@ -568,3 +346,44 @@ img.emoji {
.appear-data {
display: none;
}
+
+.chart {
+ overflow: hidden;
+ height: 220px;
+}
+
+.navless-container {
+ margin-top: 20px;
+}
+
+.description-block {
+ @extend .light-well;
+ @extend .light;
+ margin-bottom: 10px;
+}
+
+.group-name {
+ font-size: 14px;
+ line-height: 24px;
+}
+
+table {
+ td.permission-x {
+ background: #D9EDF7 !important;
+ text-align: center;
+ }
+}
+
+.dashboard-intro-icon {
+ float: left;
+ font-size: 32px;
+ color: #AAA;
+ padding: 5px 0;
+ width: 50px;
+ min-height: 100px;
+}
+
+.navbar-gitlab .navbar-inner .nav > li .btn-sign-in {
+ @extend .btn-new;
+ padding: 5px 15px;
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap.scss b/app/assets/stylesheets/gitlab_bootstrap.scss
index 2ad1bf944a9..faf36b702c0 100644
--- a/app/assets/stylesheets/gitlab_bootstrap.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap.scss
@@ -2,11 +2,49 @@
$baseFontSize: 13px !default;
$baseLineHeight: 18px !default;
-// BOOTSTRAP
-@import "bootstrap";
+/**
+ * BOOTSTRAP
+ */
+@import "bootstrap/variables";
+@import "bootstrap/mixins";
+@import "bootstrap/reset";
+@import "bootstrap/scaffolding";
+@import "bootstrap/grid";
+@import "bootstrap/layouts";
+@import "bootstrap/type";
+@import "bootstrap/code";
+@import "bootstrap/forms";
+@import "bootstrap/tables";
+@import "bootstrap/sprites";
+@import "bootstrap/dropdowns";
+@import "bootstrap/wells";
+@import "bootstrap/component-animations";
+@import "bootstrap/close";
+@import "bootstrap/button-groups";
+@import "bootstrap/alerts";
+@import "bootstrap/navs";
+@import "bootstrap/navbar";
+@import "bootstrap/breadcrumbs";
+@import "bootstrap/pagination";
+@import "bootstrap/pager";
+@import "bootstrap/modals";
+@import "bootstrap/tooltip";
+@import "bootstrap/popovers";
+@import "bootstrap/thumbnails";
+@import "bootstrap/media";
+@import "bootstrap/labels-badges";
+@import "bootstrap/progress-bars";
+@import "bootstrap/accordion";
+@import "bootstrap/carousel";
+@import "bootstrap/hero-unit";
+@import "bootstrap/utilities";
@import "bootstrap/responsive-utilities";
@import "bootstrap/responsive-1200px-min";
+/**
+ * Font icons
+ *
+ */
@import "font-awesome";
/**
@@ -24,5 +62,5 @@ $baseLineHeight: 18px !default;
@import "gitlab_bootstrap/buttons.scss";
@import "gitlab_bootstrap/blocks.scss";
@import "gitlab_bootstrap/files.scss";
-@import "gitlab_bootstrap/tables.scss";
@import "gitlab_bootstrap/lists.scss";
+@import "gitlab_bootstrap/forms.scss";
diff --git a/app/assets/stylesheets/gitlab_bootstrap/avatar.scss b/app/assets/stylesheets/gitlab_bootstrap/avatar.scss
index de1fb1551bf..c23970c13eb 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/avatar.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/avatar.scss
@@ -1,8 +1,22 @@
-/** AVATARS **/
-img.avatar { float: left; margin-right: 12px; width: 40px; border: 1px solid #ddd; padding: 1px; }
-img.avatar.s16 { width: 16px; height: 16px; margin-right: 6px; }
-img.avatar.s24 { width: 24px; height: 24px; margin-right: 8px; }
-img.avatar.s32 { width: 32px; height: 32px; margin-right: 10px; }
-img.avatar.s90 { width: 90px; height: 90px; margin-right: 15px; }
-img.lil_av { padding-left: 4px; padding-right: 3px; }
-img.small { width: 80px; }
+.avatar {
+ float: left;
+ margin-right: 12px;
+ width: 40px;
+ border: 1px solid #ddd;
+ padding: 1px;
+
+ &.avatar-inline {
+ float: none;
+ margin-left: 3px;
+
+ &.s16 { margin-right: 2px; }
+ &.s24 { margin-right: 2px; }
+ }
+
+ &.s16 { width: 16px; height: 16px; margin-right: 6px; }
+ &.s24 { width: 24px; height: 24px; margin-right: 8px; }
+ &.s26 { width: 26px; height: 26px; margin-right: 8px; }
+ &.s32 { width: 32px; height: 32px; margin-right: 10px; }
+ &.s60 { width: 60px; height: 60px; margin-right: 12px; }
+ &.s90 { width: 90px; height: 90px; margin-right: 15px; }
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss
index 4d1b6446362..fcf1159cd33 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss
@@ -10,14 +10,43 @@
*
*/
.ui-box {
- background: #F9F9F9;
- margin-bottom: 25px;
+ background: #FFF;
+ margin-bottom: 20px;
border: 1px solid #CCC;
- @include solid-shade;
+ word-wrap: break-word;
+
+ &.small-box {
+ margin-bottom: 10px;
+
+ .title {
+ font-size: 13px;
+ line-height: 30px;
+
+ a {
+ color: #666;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+ }
&.ui-box-show {
margin:20px 0;
background: #FFF;
+
+ .control-group {
+ margin-bottom: 0;
+ }
+ }
+
+ &.ui-box-danger {
+ .title {
+ @include linear-gradient(#F26E5E, #bd362f);
+ color: #fff;
+ text-shadow: 0 1px 1px #900;
+ font-weight: bold;
+ }
}
img { max-width: 100%; }
@@ -32,14 +61,6 @@
.ui-box-body,
.ui-box-bottom {
padding: 15px;
- word-wrap: break-word;
-
- pre {
- background: none !important;
- margin: 0;
- border: none;
- padding: 0;
- }
.clearfix {
margin: 0;
@@ -52,6 +73,7 @@
font-size: 18px;
font-weight: normal;
line-height: 28px;
+ margin: 0;
}
h3 {
margin: 0;
@@ -60,7 +82,6 @@
.ui-box-body {
border: none;
- font-size: 12px;
background-color: #f5f5f5;
border: none;
border-top: 1px solid #eee;
@@ -70,10 +91,6 @@
border-top: 1px solid #eee;
}
- &.white {
- background: #fff;
- }
-
ul {
margin: 0;
}
@@ -84,10 +101,11 @@
color: #456;
font-size: 16px;
text-shadow: 0 1px 1px #fff;
- padding: 0px 10px;
- line-height: 36px;
+ padding: 0 10px;
font-size: 14px;
+ line-height: 40px;
font-weight: normal;
+ margin: 0;
> a {
text-shadow: 0 1px 1px #fff;
@@ -98,8 +116,10 @@
margin-top: 0;
}
- .btn-tiny {
- @include box-shadow(0 0px 0px 1px #f1f1f1);
+ .btn {
+ vertical-align: middle;
+ padding: 4px 12px;
+ @include box-shadow(0 0px 1px 1px #f2f2f2);
}
.nav-pills {
@@ -129,15 +149,6 @@
margin-bottom: 0;
padding: 5px 20px;
}
- .middle_title {
- background: #f5f5f5;
- margin:20px -20px;
- padding: 0 20px;
- border-top: 1px solid #eee;
- border-bottom: 1px solid #eee;
- font-size: 14px;
- color: #777;
- }
}
.row_title {
@@ -148,4 +159,28 @@
text-decoration: underline;
}
}
+
+ .form-holder {
+ padding-top: 20px;
+ form {
+ margin-bottom: 0;
+ legend {
+ text-indent: 10px;
+ }
+ .form-actions {
+ margin-bottom: 0;
+ }
+ }
+ }
+}
+
+.tab-pane {
+ .ui-box {
+ margin: 3px 3px 25px 3px;
+ }
+}
+
+.light-well {
+ background: #f9f9f9;
+ padding: 15px;
}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss
index 03497e32d26..9eb32ca95e6 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss
@@ -1,65 +1,106 @@
.btn {
+ display: inline-block;
+ padding: 6px 12px;
+ margin-bottom: 0;
+ font-size: 13px;
+ line-height: $baseLineHeight;
+ text-align: center;
+ vertical-align: middle;
+ cursor: pointer;
+ border: 1px solid #BBB;
+ color: $style_color;
+ @include border-radius($baseBorderRadius);
+ @include box-shadow(inset 0 1px 0 rgba(255,255,255,.2));
@include linear-gradient(#f1f1f1, #e1e1e1);
text-shadow: 0 1px 1px #FFF;
- border-color: #BBB;
+ text-decoration: none;
+ &.hover,
&:hover {
+ color: $style_color;
background: #f1f1f1;
- @include linear-gradient(#fAfAfA, #f1f1f1);
border-color: #AAA;
- color: #333;
+ text-decoration: none;
+ @include linear-gradient(#fAfAfA, #f1f1f1);
}
- &.btn-primary {
- background: #2a79A3;
- @include linear-gradient(#47A7b7, #2585b5);
- border-color: #2A79A3;
- color: #fff;
- text-shadow: 0 1px 1px #268;
- &:hover {
- background: $primary_color;
- color: #fff;
- }
+ &.focus,
+ &:focus {
+ text-decoration: none;
+ @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15));
+ }
- &.disabled {
- color: #fff;
- background: #29B;
- }
+ &.active,
+ &:active {
+ background-image: none;
+ outline: 0;
+ text-decoration: none;
+ @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15));
}
- &.btn-info {
- background: #5aB9C3;
- border-color: $primary_color;
- color: #fff;
- text-shadow: 0 1px 1px #268;
- &:hover {
- background: $primary_color;
- color: #fff;
- }
+ &.disabled,
+ &[disabled] {
+ cursor: default;
+ background-image: none;
+ @include opacity(65);
+ @include box-shadow(none);
+ }
- &.disabled {
- color: #fff;
- background: #29B;
+ &.btn-primary {
+ color: #FFF;
+ border-color: #189;
+ text-shadow: 0 1px 1px #189;
+ @include linear-gradient(#4AC, #289);
+
+ &.hover,
+ &:hover,
+ &.disabled,
+ &[disabled] {
+ color: #FFF;
+ background: #389;
}
}
- &.success {
- @extend .btn-success;
+ &.btn-success {
+ color: #FFF;
+ border-color: #1A1;
+ text-shadow: 0 1px 1px #FFF;
+ text-shadow: 0 1px 1px #181;
+ @include linear-gradient(#62C452, #51a351);
- &:hover {
- @extend .btn-success;
- background: #51a351;
+
+ &.hover,
+ &:hover,
+ &.disabled,
+ &[disabled] {
+ color: #FFF;
+ background: #2A2;
}
+ }
+
+ &.btn-danger {
+ color: #FFF;
+ text-shadow: 0 1px 1px #811;
+ border-color: #BD362F;
+ @include linear-gradient(#EE5F5B, #BD362F);
+
- &.disabled {
- color: #fff;
- background: #2b2;
+ &.hover,
+ &:hover,
+ &.disabled,
+ &[disabled] {
+ color: #FFF;
+ background: #A22;
}
}
+ &.btn-new {
+ @extend .btn-success;
+ }
+
&.btn-create {
@extend .wide;
- @extend .success;
+ @extend .btn-success;
}
&.btn-save {
@@ -70,12 +111,6 @@
&.btn-close,
&.btn-remove {
@extend .btn-danger;
- border-color: #BD362F;
-
- &:hover {
- color: #fff;
- background: #EE4E49;
- }
}
&.btn-cancel {
@@ -87,13 +122,9 @@
padding-right: 20px;
}
- &.small {
- @extend .btn-small;
- }
-
- &.active {
- border-color: #aaa;
- background-color: #ccc;
+ &.btn-small {
+ padding: 2px 10px;
+ font-size: 12px;
}
&.btn-tiny {
@@ -107,9 +138,4 @@
margin-right: 7px;
float: left;
}
-
- &.padded {
- margin-right: 3px;
- padding: 4px 10px 4px;
- }
}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss
index dcfd610e2c4..bc6c786da50 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/common.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss
@@ -1,18 +1,26 @@
/** COLORS **/
-.cgray { color:gray }
-.cred { color:#D12F19 }
-.cgreen { color:#4a2 }
-.cblack { color:#111 }
-.cdark { color:#444 }
-.cwhite { color:#fff!important }
-.bgred { background:#F2DEDE!important }
+.cgray { color: gray }
+.cred { color: #D12F19 }
+.cgreen { color: #4a2 }
+.cblue { color: #29A }
+.cblack { color: #111 }
+.cdark { color: #444 }
+.cwhite { color: #fff!important }
+.bgred { background: #F2DEDE!important }
/** COMMON CLASSES **/
.left { float:left }
-.append-bottom-10 { margin-bottom:10px }
-.append-bottom-20 { margin-bottom:20px }
+
.prepend-top-10 { margin-top:10px }
.prepend-top-20 { margin-top:20px }
+.prepend-left-10 { margin-left:10px }
+.prepend-left-20 { margin-left:20px }
+.append-right-10 { margin-right:10px }
+.append-right-20 { margin-right:20px }
+.append-bottom-10 { margin-bottom:10px }
+.append-bottom-20 { margin-bottom:20px }
+.inline { display: inline-block }
+
.padded { padding:20px }
.ipadded { padding:20px!important }
.lborder { border-left:1px solid #eee }
@@ -20,7 +28,7 @@
.hint { font-style: italic; color: #999; }
.light { color: #888 }
.tiny { font-weight: normal }
-.vtop { vertical-align: top; }
+.vtop { vertical-align: top !important; }
/** ALERT MESSAGES **/
@@ -40,33 +48,42 @@
line-height: 36px;
}
-p.slead { color: #456; font-size: 16px; margin-bottom: 12px; font-weight: 200; line-height: 24px; }
+.slead {
+ color: #666;
+ font-size: 14px;
+ margin-bottom: 12px;
+ font-weight: normal;
+ line-height: 24px;
+}
+
+
+.tab-content {
+ overflow: visible;
+}
-/** FORMS **/
-input[type='search'].search-text-input {
- background-image: url("icon-search.png");
- background-repeat: no-repeat;
- background-position: 10px;
- padding-left: 25px;
- @include border-radius(4px);
- border: 1px solid #ccc;
+@media (max-width: 1200px) {
+ .only-wide {
+ display: none;
+ }
}
-input[type='text'].danger {
- background: #F2DEDE!important;
- border-color: #D66;
- text-shadow: 0 1px 1px #fff
+.pagination ul > li > a, .pagination ul > li >span {
+ @include linear-gradient(#f1f1f1, #e1e1e1);
+ color: #333;
+ text-shadow: 0 1px 1px #FFF;
}
-fieldset legend { font-size: 17px; }
+pre.well-pre {
+ border: 1px solid #EEE;
+ background: #f9f9f9;
+ border-radius: 0;
+ color: #555;
+}
-/** PAGINATION **/
-.gitlab_pagination {
- span a { color: $link_color; }
- .prev, .next, .current, .page a {
- padding: 10px;
- }
- .current {
- border-bottom: 2px solid $style_color;
- }
+.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));
}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/gitlab_bootstrap/files.scss
index 279cfcd2103..8ba8c93e3d6 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/files.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/files.scss
@@ -2,19 +2,23 @@
* File content holder
*
*/
-.file_holder {
- border: 1px solid #BBB;
+.file-holder {
+ border: 1px solid #CCC;
margin-bottom: 1em;
- @include solid-shade;
- .file_title {
+ table {
+ @extend .table;
+ }
+
+ .file-title {
border-bottom: 1px solid #bbb;
@include bg-dark-gray-gradient;
+ text-shadow: 0 1px 1px #fff;
margin: 0;
font-weight: normal;
font-weight: bold;
text-align: left;
- color: #666;
+ color: $style_color;
padding: 9px 10px;
height: 18px;
@@ -33,7 +37,7 @@
}
}
}
- .file_content {
+ .file-content {
background: #fff;
font-size: 11px;
@@ -48,7 +52,17 @@
&.wiki {
padding: 20px;
- font-size: 13px;
+ font-size: 14px;
+ line-height: 1.6;
+
+ .highlight {
+ margin-bottom: 9px;
+ @include border-radius(4px);
+
+ > pre {
+ margin: 0;
+ }
+ }
}
&.blob_file {
@@ -162,6 +176,7 @@
color: #666;
padding: 10px 6px 10px 0;
text-align: right;
+ background: #EEE;
a {
color: #666;
diff --git a/app/assets/stylesheets/gitlab_bootstrap/fonts.scss b/app/assets/stylesheets/gitlab_bootstrap/fonts.scss
index a0c9a6c7b8a..8cc9986415c 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/fonts.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/fonts.scss
@@ -1,7 +1,2 @@
-@font-face{
- font-family: Yanone;
- src: font-url('YanoneKaffeesatz-Light.ttf');
-}
-
/** Typo **/
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
diff --git a/app/assets/stylesheets/gitlab_bootstrap/forms.scss b/app/assets/stylesheets/gitlab_bootstrap/forms.scss
new file mode 100644
index 00000000000..a2612166c78
--- /dev/null
+++ b/app/assets/stylesheets/gitlab_bootstrap/forms.scss
@@ -0,0 +1,51 @@
+form {
+ @extend .form-horizontal;
+
+ label {
+ @extend .control-label;
+ }
+}
+
+input.input-xpadding,
+.add-on.input-xpadding {
+ padding: 6px 10px;
+}
+
+.control-group {
+ .control-label {
+ padding-top: 6px;
+ }
+ .controls {
+ input, textarea {
+ padding: 6px 10px;
+ }
+
+ input[type="radio"], input[type="checkbox"] {
+ margin-top: 6px;
+ }
+
+ .add-on {
+ padding: 6px;
+ }
+ }
+}
+
+input[type='search'].search-text-input {
+ background-image: url("icon-search.png");
+ background-repeat: no-repeat;
+ background-position: 10px;
+ padding-left: 25px;
+ @include border-radius(4px);
+ border: 1px solid #ccc;
+}
+
+input[type='text'].danger {
+ background: #F2DEDE!important;
+ border-color: #D66;
+ text-shadow: 0 1px 1px #fff
+}
+
+fieldset legend {
+ font-size: 16px;
+ margin-bottom: 10px;
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/lists.scss b/app/assets/stylesheets/gitlab_bootstrap/lists.scss
index 0f893a553ee..83066b5beec 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/lists.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/lists.scss
@@ -6,7 +6,6 @@
margin: 0;
list-style: none;
li {
- background-color: #FFF;
padding: 10px;
min-height: 20px;
border-bottom: 1px solid #eee;
@@ -16,6 +15,12 @@
color: #888;
}
+ &.unstyled {
+ &:hover {
+ background: none;
+ }
+ }
+
&.smoke { background-color: #f5f5f5; }
&:hover {
@@ -69,5 +74,22 @@ ul.bordered-list {
display: block;
margin: 0px;
&:last-child { border:none }
+ &.active {
+ background: #f9f9f9;
+ a { font-weight: bold; }
+ }
+
+ &.light {
+ a { color: #777; }
+ }
+ }
+
+ &.top-list {
+ li:first-child {
+ padding-top: 0;
+ h4, h5 {
+ margin-top: 0;
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/mixins.scss b/app/assets/stylesheets/gitlab_bootstrap/mixins.scss
index 9b1e2f2c728..8b975a12cf7 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/mixins.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/mixins.scss
@@ -17,6 +17,10 @@
border-radius: $radius;
}
+@mixin border-radius-left($radius) {
+ @include border-radius($radius 0 0 $radius)
+}
+
@mixin linear-gradient($from, $to) {
background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to));
background-image: -webkit-linear-gradient($from, $to);
@@ -24,6 +28,14 @@
background-image: -o-linear-gradient($from, $to);
}
+@mixin transition($transition) {
+ -webkit-transition: $transition;
+ -moz-transition: $transition;
+ -ms-transition: $transition;
+ -o-transition: $transition;
+ transition: $transition;
+}
+
/**
* Prefilled mixins
* Mixins with fixed values
@@ -62,8 +74,41 @@
@mixin header-font {
color: $style_color;
text-shadow: 0 1px 1px #FFF;
- font-family: 'Yanone', sans-serif;
- font-size: 26px;
- line-height: 42px;
+ font-size: 16px;
+ line-height: 40px;
font-weight: normal;
}
+
+@mixin md-typography {
+ *:first-child {
+ margin-top: 0;
+ }
+
+ code { padding: 0 4px; }
+ h1 { margin-top: 30px;}
+ h2 { margin-top: 25px;}
+ h3 { margin-top: 20px;}
+ h4 { margin-top: 15px;}
+
+ blockquote p {
+ color: #888;
+ font-size: 14px;
+ line-height: 1.5;
+ }
+
+ table {
+ @extend .table;
+ @extend .table-bordered;
+ th {
+ background: #EEE;
+ }
+ }
+}
+
+@mixin page-title {
+ color: $style_color;
+ font-size: 20px;
+ line-height: 1.5;
+ margin-top: 0px;
+ margin-bottom: 15px;
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/nav.scss b/app/assets/stylesheets/gitlab_bootstrap/nav.scss
index 2eaef61ca33..aa4cb1ed5fd 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/nav.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/nav.scss
@@ -10,17 +10,32 @@
> li > a {
@include border-radius(0);
}
+
&.nav-stacked {
> li > a {
border-left: 4px solid #EEE;
padding: 12px;
}
> .active > a {
- border-color: #29B;
+ border-color: $primary_color;
border-radius: 0;
background: #F1F1F1;
color: $style_color;
font-weight: bold;
+ text-shadow: 0 1px 1px #fff;
+ }
+
+ &.nav-stacked-menu {
+ background: #FAFAFA;
+ li > a {
+ padding: 16px;
+ }
+ }
+ }
+
+ &.nav-pills-small {
+ > li > a {
+ padding: 8px 12px;
}
}
}
@@ -57,9 +72,21 @@
border-color: #CCC;
border-bottom: 1px solid #fff;
color: #333;
+ font-weight: bold;
}
}
}
&.nav-small-tabs > li > a { padding: 6px 9px; }
}
+
+
+
+/**
+ * fix to keep tooltips position in top navigation bar
+ *
+ */
+.navbar .nav > li {
+ position: relative;
+ white-space: nowrap;
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/tables.scss b/app/assets/stylesheets/gitlab_bootstrap/tables.scss
deleted file mode 100644
index 7a9eac82566..00000000000
--- a/app/assets/stylesheets/gitlab_bootstrap/tables.scss
+++ /dev/null
@@ -1,63 +0,0 @@
-table {
- @extend .table;
- @extend .table-striped;
- @include solid-shade;
- border: 1px solid #bbb;
- width: 100%;
-
- &.low {
- td {
- line-height: 18px;
- }
- }
-
- th {
- font-weight: bold;
- vertical-align: middle;
- border-bottom: 1px solid #bbb;
- text-shadow: 0 1px 1px #fff;
- @include bg-dark-gray-gradient;
-
- ul.nav {
- text-shadow: none;
- margin: 0;
- }
- }
-
- th, td {
- padding: 10px;
- line-height: 18px;
- text-align: left;
- }
-
- td {
- border-color: #f1f1f1;
- line-height: 28px;
-
- .s16 {
- margin-top: 5px;
- margin-right: 5px;
- }
-
- &:first-child {
- border-left: 1px solid #bbb;
- }
-
- &:last-child {
- border-right: 1px solid #bbb;
- }
- }
-
- &.bordered {
- @extend .table-bordered;
- }
-
- &.lite {
- border: none;
- box-shadow: none;
- tr, td {
- border: none;
- background:none !important;
- }
- }
-}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/typography.scss b/app/assets/stylesheets/gitlab_bootstrap/typography.scss
index 781577c2147..d3986556376 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/typography.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/typography.scss
@@ -2,16 +2,23 @@
* Headers
*
*/
+h1, h2, h3, h4, h5, h6 {
+ font-weight: 500;
+ line-height: 1.1;
+}
+
+h1.page-title {
+ @include page-title;
+ font-size: 28px;
+}
-h1, h2, h3, h4, h5, h6 { margin: 0; }
-h3, h4, h5, h6 { line-height: 36px; }
-h5 { font-size: 14px; }
+h2.page-title {
+ @include page-title;
+ font-size: 24px;
+}
-h3.page_title {
- color: #456;
- font-size: 20px;
- font-weight: normal;
- line-height: 28px;
+h3.page-title {
+ @include page-title;
}
h6 {
@@ -41,11 +48,8 @@ a {
color: $primary_color;
}
- &.btn {
- color: $style_color;
- &:hover {
- color: $style_color;
- }
+ &:focus {
+ text-decoration: underline;
}
&.dark {
@@ -87,16 +91,18 @@ a:focus {
*
*/
.wiki {
- font-size: 13px;
-
- code { padding: 0 4px; }
- p { font-size: 13px; }
- h1 { font-size: 32px; line-height: 40px; margin: 10px 0;}
- h2 { font-size: 26px; line-height: 40px; margin: 10px 0;}
- h3 { font-size: 22px; line-height: 40px; margin: 10px 0;}
- h4 { font-size: 18px; line-height: 20px; margin: 10px 0;}
- h5 { font-size: 14px; line-height: 20px; margin: 10px 0;}
- h6 { font-size: 12px; line-height: 20px; margin: 10px 0;}
- .white .highlight pre { background: #f5f5f5; }
- ul { margin: 0 0 9px 25px !important; }
+ @include md-typography;
+
+ font-size: 14px;
+ line-height: 1.6;
+ .white .highlight pre {
+ background: #f5f5f5;
+ }
+ ul {
+ margin: 0 0 9px 25px !important;
+ }
+}
+
+.md {
+ @include md-typography;
}
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 6018ff7074d..129d33dcac3 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -1,8 +1,10 @@
-.black .highlight {
+.dark .highlight {
+
background-color: #333;
+
pre {
+ background-color: #333;
color: #eee;
- background: inherit;
}
.hll { display: block; background-color: darken($hover, 65%) }
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
new file mode 100644
index 00000000000..c9709fa7f12
--- /dev/null
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -0,0 +1,89 @@
+$monokai-fg: #f8f8f2;
+$monokai-comment: #75715e;
+$monokai-pink: #f92672;
+$monokai-blue: #66d9ef;
+$monokai-green: #a6e22e;
+$monokai-gold: #e6db74;
+$monokai-dark: #3b3a32;
+$monokai-purple: #ae81ff;
+
+.monokai .highlight {
+
+ background-color: #272822;
+
+ pre {
+ background-color: #272822;
+ color: $monokai-fg;
+ }
+
+ .hll { background-color: darken($hover, 65%) }
+ .c { color: $monokai-comment } /* Comment */
+ .err { color: $monokai-fg } /* Error */
+ .g { color: $monokai-fg } /* Generic */
+ .k { color: $monokai-pink } /* Keyword */
+ .l { color: $monokai-fg } /* Literal */
+ .n { color: $monokai-blue } /* Name */
+ .o { color: $monokai-fg } /* Operator */
+ .x { color: $monokai-fg } /* Other */
+ .p { color: $monokai-fg } /* Punctuation */
+ .cm { color: $monokai-comment } /* Comment.Multiline */
+ .cp { color: $monokai-comment } /* Comment.Preproc */
+ .c1 { color: $monokai-comment } /* Comment.Single */
+ .cs { color: $monokai-comment } /* Comment.Special */
+ .gd { color: #8b0807 } /* Generic.Deleted */
+ .ge { color: $monokai-fg; text-decoration: underline } /* Generic.Emph */
+ .gr { color: $monokai-fg } /* Generic.Error */
+ .gh { color: $monokai-fg; font-weight: bold } /* Generic.Heading */
+ .gi { color: $monokai-fg; font-weight: bold; background-color: #46830c } /* Generic.Inserted */
+ .go { color: $monokai-dark; background-color: #31322c } /* Generic.Output */
+ .gp { color: $monokai-fg } /* Generic.Prompt */
+ .gs { color: $monokai-fg } /* Generic.Strong */
+ .gu { color: $monokai-fg; font-weight: bold } /* Generic.Subheading */
+ .gt { color: #f8f8f0; background-color: $monokai-pink } /* Generic.Traceback */
+ .kc { color: $monokai-purple } /* Keyword.Constant */
+ .kd { color: $monokai-pink } /* Keyword.Declaration */
+ .kn { color: $monokai-pink } /* Keyword.Namespace */
+ .kp { color: $monokai-pink } /* Keyword.Pseudo */
+ .kr { color: $monokai-pink } /* Keyword.Reserved */
+ .kt { color: $monokai-fg } /* Keyword.Type */
+ .ld { color: $monokai-fg } /* Literal.Date */
+ .m { color: $monokai-purple } /* Literal.Number */
+ .s { color: $monokai-gold } /* Literal.String */
+ .na { color: $monokai-purple } /* Name.Attribute */
+ .nb { color: $monokai-blue } /* Name.Builtin */
+ .nc { color: $monokai-fg } /* Name.Class */
+ .no { color: $monokai-fg } /* Name.Constant */
+ .nd { color: $monokai-fg } /* Name.Decorator */
+ .ni { color: $monokai-fg } /* Name.Entity */
+ .ne { color: $monokai-fg } /* Name.Exception */
+ .nf { color: $monokai-green } /* Name.Function */
+ .nl { color: $monokai-gold } /* Name.Label */
+ .nn { color: $monokai-fg } /* Name.Namespace */
+ .nx { color: $monokai-fg } /* Name.Other */
+ .nt { color: $monokai-pink } /* Name.Tag */
+ .nv { color: $monokai-blue; font-style: italic } /* Name.Variable */
+ .py { color: $monokai-fg } /* Name.Property */
+ .ow { color: $monokai-pink } /* Operator.Word */
+ .w { color: $monokai-fg } /* Text.Whitespace */
+ .mf { color: $monokai-purple } /* Literal.Number.Float */
+ .mh { color: $monokai-purple } /* Literal.Number.Hex */
+ .mi { color: $monokai-purple } /* Literal.Number.Integer */
+ .mo { color: $monokai-purple } /* Literal.Number.Oct */
+ .sb { color: $monokai-gold } /* Literal.String.Backtick */
+ .sc { color: $monokai-gold } /* Literal.String.Char */
+ .sd { color: $monokai-gold } /* Literal.String.Doc */
+ .s2 { color: $monokai-gold } /* Literal.String.Double */
+ .se { color: $monokai-gold } /* Literal.String.Escape */
+ .sh { color: $monokai-gold } /* Literal.String.Heredoc */
+ .si { color: $monokai-gold } /* Literal.String.Interpol */
+ .sx { color: $monokai-gold } /* Literal.String.Other */
+ .sr { color: $monokai-gold } /* Literal.String.Regex */
+ .s1 { color: $monokai-gold } /* Literal.String.Single */
+ .ss { color: $monokai-gold } /* Literal.String.Symbol */
+ .bp { color: $monokai-fg } /* Name.Builtin.Pseudo */
+ .vc { color: $monokai-blue; font-style: italic } /* Name.Variable.Class */
+ .vg { color: $monokai-blue; font-style: italic } /* Name.Variable.Global */
+ .vi { color: $monokai-blue; font-style: italic } /* Name.Variable.Instance */
+ .il { color: $monokai-purple } /* Literal.Number.Integer.Long */
+}
+
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
new file mode 100644
index 00000000000..cc82f39ac93
--- /dev/null
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -0,0 +1,80 @@
+.solarized-dark .highlight {
+
+ background-color: #002B36;
+
+ pre {
+ background-color: #002B36;
+ color: #eee;
+ }
+
+ .hll { background-color: #073642 }
+ .c { color: #586E75 } /* Comment */
+ .err { color: #93A1A1 } /* Error */
+ .g { color: #93A1A1 } /* Generic */
+ .k { color: #859900 } /* Keyword */
+ .l { color: #93A1A1 } /* Literal */
+ .n { color: #93A1A1 } /* Name */
+ .o { color: #859900 } /* Operator */
+ .x { color: #CB4B16 } /* Other */
+ .p { color: #93A1A1 } /* Punctuation */
+ .cm { color: #586E75 } /* Comment.Multiline */
+ .cp { color: #859900 } /* Comment.Preproc */
+ .c1 { color: #586E75 } /* Comment.Single */
+ .cs { color: #859900 } /* Comment.Special */
+ .gd { color: #2AA198 } /* Generic.Deleted */
+ .ge { color: #93A1A1; font-style: italic } /* Generic.Emph */
+ .gr { color: #DC322F } /* Generic.Error */
+ .gh { color: #CB4B16 } /* Generic.Heading */
+ .gi { color: #859900 } /* Generic.Inserted */
+ .go { color: #93A1A1 } /* Generic.Output */
+ .gp { color: #93A1A1 } /* Generic.Prompt */
+ .gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */
+ .gu { color: #CB4B16 } /* Generic.Subheading */
+ .gt { color: #93A1A1 } /* Generic.Traceback */
+ .kc { color: #CB4B16 } /* Keyword.Constant */
+ .kd { color: #268BD2 } /* Keyword.Declaration */
+ .kn { color: #859900 } /* Keyword.Namespace */
+ .kp { color: #859900 } /* Keyword.Pseudo */
+ .kr { color: #268BD2 } /* Keyword.Reserved */
+ .kt { color: #DC322F } /* Keyword.Type */
+ .ld { color: #93A1A1 } /* Literal.Date */
+ .m { color: #2AA198 } /* Literal.Number */
+ .s { color: #2AA198 } /* Literal.String */
+ .na { color: #93A1A1 } /* Name.Attribute */
+ .nb { color: #B58900 } /* Name.Builtin */
+ .nc { color: #268BD2 } /* Name.Class */
+ .no { color: #CB4B16 } /* Name.Constant */
+ .nd { color: #268BD2 } /* Name.Decorator */
+ .ni { color: #CB4B16 } /* Name.Entity */
+ .ne { color: #CB4B16 } /* Name.Exception */
+ .nf { color: #268BD2 } /* Name.Function */
+ .nl { color: #93A1A1 } /* Name.Label */
+ .nn { color: #93A1A1 } /* Name.Namespace */
+ .nx { color: #93A1A1 } /* Name.Other */
+ .py { color: #93A1A1 } /* Name.Property */
+ .nt { color: #268BD2 } /* Name.Tag */
+ .nv { color: #268BD2 } /* Name.Variable */
+ .ow { color: #859900 } /* Operator.Word */
+ .w { color: #93A1A1 } /* Text.Whitespace */
+ .mf { color: #2AA198 } /* Literal.Number.Float */
+ .mh { color: #2AA198 } /* Literal.Number.Hex */
+ .mi { color: #2AA198 } /* Literal.Number.Integer */
+ .mo { color: #2AA198 } /* Literal.Number.Oct */
+ .sb { color: #586E75 } /* Literal.String.Backtick */
+ .sc { color: #2AA198 } /* Literal.String.Char */
+ .sd { color: #93A1A1 } /* Literal.String.Doc */
+ .s2 { color: #2AA198 } /* Literal.String.Double */
+ .se { color: #CB4B16 } /* Literal.String.Escape */
+ .sh { color: #93A1A1 } /* Literal.String.Heredoc */
+ .si { color: #2AA198 } /* Literal.String.Interpol */
+ .sx { color: #2AA198 } /* Literal.String.Other */
+ .sr { color: #DC322F } /* Literal.String.Regex */
+ .s1 { color: #2AA198 } /* Literal.String.Single */
+ .ss { color: #2AA198 } /* Literal.String.Symbol */
+ .bp { color: #268BD2 } /* Name.Builtin.Pseudo */
+ .vc { color: #268BD2 } /* Name.Variable.Class */
+ .vg { color: #268BD2 } /* Name.Variable.Global */
+ .vi { color: #268BD2 } /* Name.Variable.Instance */
+ .il { color: #2AA198 } /* Literal.Number.Integer.Long */
+}
+
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index f200e1d7b60..df127a7c491 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -1,4 +1,7 @@
.white .highlight {
+
+ background-color: #fff;
+
pre {
background-color: #fff;
color: #333;
diff --git a/app/assets/stylesheets/ref_select.scss b/app/assets/stylesheets/ref_select.scss
deleted file mode 100644
index 284d1c324e5..00000000000
--- a/app/assets/stylesheets/ref_select.scss
+++ /dev/null
@@ -1,90 +0,0 @@
-/** Branch/tag selector **/
-.project-refs-form {
- margin: 0;
- span {
- background:none !important;
- position:static !important;
- width:auto !important;
- height:auto !important;
- }
-}
-.project-refs-select {
- width: 120px;
-}
-
-.project-refs-form .chzn-container {
- position: relative;
- top: 0;
- left: 0;
- margin-right: 10px;
-
- .chzn-drop {
- min-width: 400px;
- .chzn-results {
- max-height: 300px;
- }
- .chzn-search input {
- min-width: 365px;
- }
- }
-}
-
-/** Fix for Search Dropdown Border **/
-.chzn-container {
- .chzn-search {
- input:focus {
- @include box-shadow(none);
- }
- }
-
- .chzn-drop {
- margin: 7px 0;
- min-width: 200px;
- border: 1px solid #bbb;
- @include border-radius(0);
-
- .chzn-results {
- margin-top: 5px;
- max-height: 300px;
-
- .group-result {
- color: $style_color;
- border-bottom: 1px solid #EEE;
- padding: 8px;
- }
- .active-result {
- @include border-radius(0);
-
- &.highlighted {
- background: $hover;
- color: $style_color;
- }
- &.result-selected {
- background: #EEE;
- border-left: 4px solid #CCC;
- }
- }
- }
-
- .chzn-search {
- @include bg-gray-gradient;
- input {
- min-width: 165px;
- border-color: #CCC;
- }
- }
- }
-
- .chzn-single {
- @include bg-light-gray-gradient;
-
- div {
- background: transparent;
- border-left: none;
- }
-
- span {
- font-weight: normal;
- }
- }
-}
diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/sections/admin.scss
index 18b102d7022..e189fd27ac6 100644
--- a/app/assets/stylesheets/sections/admin.scss
+++ b/app/assets/stylesheets/sections/admin.scss
@@ -1,3 +1,21 @@
+/**
+ * Admin area
+ *
+ */
+.admin_dash {
+ .data {
+ a {
+ h1 {
+ line-height: 48px;
+ font-size: 48px;
+ padding: 20px;
+ text-align: center;
+ font-weight: normal;
+ }
+ }
+ }
+}
+
.admin-filter form {
label { width: 110px; }
.controls { margin-left: 130px; }
diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss
index 8b93287ed1e..be6fb29c817 100644
--- a/app/assets/stylesheets/sections/commits.scss
+++ b/app/assets/stylesheets/sections/commits.scss
@@ -29,7 +29,7 @@
a{
color: $style_color;
}
-
+
> span {
font-family: $monospace_font;
font-size: 14px;
@@ -67,6 +67,7 @@
}
table {
+ width: 100%;
font-family: $monospace_font;
border: none;
margin: 0px;
@@ -99,11 +100,24 @@
}
}
}
+ .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: 14px;
+ height: 18px;
margin: 0px;
- padding: 0px;
+ padding: 0px 0.5em;
border: none;
&.new {
background: #CFD;
@@ -124,7 +138,7 @@
.wrap{
display: inline-block;
}
-
+
.frame {
display: inline-block;
background-color: #fff;
@@ -149,7 +163,7 @@
.view.swipe{
position: relative;
-
+
.swipe-frame{
display: block;
margin: auto;
@@ -228,7 +242,7 @@
bottom: 0px;
left: 50%;
margin-left: -150px;
-
+
.drag-track{
display: block;
position: absolute;
@@ -237,7 +251,7 @@
width: 276px;
background: url('onion_skin_sprites.gif') -4px -20px repeat-x;
}
-
+
.dragger {
display: block;
position: absolute;
@@ -248,7 +262,7 @@
background: url('onion_skin_sprites.gif') 0px -34px repeat-x;
cursor: pointer;
}
-
+
.transparent {
display: block;
position: absolute;
@@ -258,7 +272,7 @@
width: 10px;
background: url('onion_skin_sprites.gif') -2px 0px no-repeat;
}
-
+
.opaque {
display: block;
position: absolute;
@@ -275,19 +289,19 @@
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: -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;
@@ -322,66 +336,40 @@
}
.commit-author, .commit-committer{
display: block;
- color: #999;
- font-weight: normal;
+ color: #999;
+ font-weight: normal;
font-style: italic;
}
.commit-author strong, .commit-committer strong{
- font-weight: bold;
+ font-weight: bold;
font-style: normal;
}
-/**
- * COMMIT ROW
- */
-.commit {
- .browse_code_link_holder {
- @extend .span2;
- float: right;
- }
-
- .committed_ago {
- float: right;
- @extend .cgray;
- }
-
- .notes_count {
- float: right;
- margin: -6px 8px 6px;
- }
-
- code {
- background: #FCEEC1;
- color: $style_color;
- }
-
- .commit_short_id {
- float: left;
- @extend .lined;
- min-width: 65px;
- font-family: $monospace_font;
- }
-}
-
.file-stats a {
color: $style_color;
}
.file-stats {
- .new-file{
- i{
+ .new-file {
+ a {
+ color: #090;
+ }
+ i {
color: #1BCF00;
}
}
- .renamed-file{
- i{
+ .renamed-file {
+ i {
color: #FE9300;
}
}
- .deleted-file{
- i{
- color: #FF0000;
+ .deleted-file {
+ a {
+ color: #B00;
+ }
+ i {
+ color: #EE0000;
}
}
.edit-file{
@@ -403,8 +391,8 @@
.commits-compare-switch{
background: url("switch_icon.png") no-repeat center center;
- width: 16px;
- height: 18px;
+ width: 22px;
+ height: 22px;
text-indent: -9999px;
float: left;
margin-right: 9px;
@@ -413,3 +401,108 @@
padding: 4px;
background-color: #EEE;
}
+
+.commit-description {
+ background: none;
+ border: none;
+ margin: 0;
+ padding: 0;
+ margin-top: 10px;
+}
+
+.commit-box {
+ margin: 10px 0;
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ padding: 20px 0;
+
+ .commit-title {
+ margin: 0;
+ font-size: 20px;
+ font-weight: bold;
+ }
+
+ .commit-description {
+ margin-top: 15px;
+ }
+}
+
+
+.commit-stat-summary {
+ color: #666;
+ line-height: 2;
+}
+
+.commit-breadcrumb {
+ padding: 0;
+}
+
+.commit-info-row {
+ margin-bottom: 10px;
+ .avatar {
+ @extend .avatar-inline;
+ }
+ .commit-committer-link,
+ .commit-author-link {
+ color: #444;
+ font-weight: bold;
+ }
+}
+
+.lists-separator {
+ margin: 10px 0;
+ border-top: 1px dashed #CCC;
+}
+
+/**
+ * COMMIT ROW
+ */
+li.commit {
+ padding: 8px;
+
+ .commit-row-title {
+ font-size: 14px;
+ margin-bottom: 2px;
+
+ .notes_count {
+ float: right;
+ margin-right: 10px;
+ }
+
+ .commit_short_id {
+ min-width: 65px;
+ font-family: $monospace_font;
+ }
+
+ .commit-row-message {
+ color: #555;
+ font-weight: bolder;
+ &:hover {
+ color: #444;
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .commit-row-info {
+ a {
+ color: #777;
+ }
+
+ .committed_ago {
+ float: right;
+ @extend .cgray;
+ }
+ }
+
+ &.inline-commit {
+ .commit-row-title {
+ font-size: 13px;
+ }
+
+ .committed_ago {
+ float: right;
+ @extend .cgray;
+ }
+ }
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss
new file mode 100644
index 00000000000..3f7825d71ce
--- /dev/null
+++ b/app/assets/stylesheets/sections/dashboard.scss
@@ -0,0 +1,102 @@
+.dashboard {
+ @extend .row;
+ .activities {
+ }
+
+ .side {
+ @extend .pull-right;
+
+ .ui-box {
+ margin: 0px;
+ box-shadow: none;
+
+ .nav-projects-tabs li { padding: 0; }
+ }
+ }
+}
+
+.dashboard-search-filter {
+ padding:5px;
+
+ .search-text-input {
+ float:left;
+ @extend .span2;
+ }
+ .btn {
+ margin-left: 5px;
+ float:left;
+ }
+}
+
+.dashboard {
+ .dash-filter {
+ margin: 7px 0;
+ padding: 4px 6px;
+ width: 202px;
+ float: left;
+ }
+}
+
+@media (max-width: 1200px) {
+ .dashboard .dash-filter {
+ width: 132px;
+ }
+}
+
+.dash-sidebar-tabs {
+ margin-bottom: 2px;
+ border: none;
+ margin: 0;
+
+ li {
+ &.active {
+ a {
+ @include linear-gradient(#f5f5f5, #eee);
+ border-bottom: 1px solid #EEE !important;
+ &:hover {
+ background: #eee;
+ }
+ }
+ }
+
+ a {
+ border-color: #CCC !important;
+ }
+ }
+}
+
+.project-row, .group-row {
+ padding: 10px 15px !important;
+
+ .namespace-name {
+ color: #666;
+ font-weight: bold;
+ }
+
+ .project-name, .group-name {
+ font-size: 16px;
+ }
+
+ .arrow {
+ float: right;
+ padding: 10px 5px;
+ margin: 0;
+ font-size: 20px;
+ color: #666;
+ }
+
+ .last-activity {
+ color: #AAA;
+ display: block;
+ margin-top: 5px;
+ .date {
+ color: #777;
+ }
+ }
+}
+
+.group-row {
+ .arrow {
+ padding: 2px 5px;
+ }
+}
diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss
index df8fd8d6458..39b2ad7a09c 100644
--- a/app/assets/stylesheets/sections/events.scss
+++ b/app/assets/stylesheets/sections/events.scss
@@ -31,49 +31,64 @@
*
*/
.event-item {
+ &:first-child {
+ padding-top: 0;
+ }
+
+ &.event-inline {
+ .avatar {
+ width: 16px;
+ height: 16px;
+ }
+ }
+
+ padding: 14px 0px;
border-bottom: 1px solid #eee;
.event-title {
color: #333;
- font-weight: bold;
+ font-weight: normal;
+ font-size: 15px;
.author_name {
color: #333;
}
}
.event-body {
- .commit p {
- color: #555;
- padding-top: 5px;
- }
+ margin-left: 35px;
+ margin-right: 100px;
+
.event-info {
color: #666;
}
.event-note {
- padding-top: 5px;
- padding-left: 5px;
- display: inline-block;
color: #555;
+ margin-top: 5px;
- .note-file-attach {
- margin-left: -25px;
- float: left;
- .note-image-attach {
- margin-left: 0px;
- max-width: 200px;
- }
+ pre {
+ border: none;
+ background: #f9f9f9;
+ border-radius: 0;
+ color: #555;
+ margin: 0 20px;
+ }
+
+ .note-image-attach {
+ margin-top: 4px;
+ margin-left: 0px;
+ max-width: 200px;
+ }
+
+ p:last-child {
+ margin-bottom: 0;
}
}
.event-note-icon {
color: #777;
float: left;
font-size: 16px;
- line-height: 18px;
- margin: 5px;
+ line-height: 16px;
+ margin-right: 5px;
}
}
- .avatar {
- position: relative;
- top: -3px;
- }
.event_icon {
position: relative;
float: right;
@@ -87,16 +102,7 @@
width: 20px;
}
}
- ul {
- margin-left: 50px;
- margin-bottom: 5px;
- .avatar {
- width: 18px;
- margin-top: 3px;
- }
- }
- padding: 16px 5px;
&:last-child { border:none }
.event_commits {
@@ -107,41 +113,24 @@
background: transparent;
padding: 3px;
border: none;
- font-size: 12px;
+ color: #666;
+ .commit-row-title {
+ font-size: 12px;
+ }
}
&.commits-stat {
display: block;
- margin-top: 5px;
+ padding: 3px;
+
+ &:hover {
+ background: none;
+ }
}
}
}
}
/**
- * Push event widget
- *
- */
-.event_lp {
- color: #777;
- padding: 10px;
- min-height: 22px;
- border-left: 5px solid #5AB9C3;
- margin-bottom: 20px;
- background: #f9f9f9;
-
- .avatar {
- width: 24px;
- }
-
- .btn-new-mr {
- @extend .btn-info;
- @extend .small;
- @extend .pull-right;
- margin: -3px;
- }
-}
-
-/**
* Event filter
*
*/
@@ -153,7 +142,7 @@
.filter_icon {
a {
text-align:center;
- border-left: 3px solid #29B;
+ border-left: 3px solid $primary_color;
background: #f9f9f9;
margin-bottom: 10px;
float: left;
diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/sections/graph.scss
index 5800098ade4..1e22d161bfc 100644
--- a/app/assets/stylesheets/sections/graph.scss
+++ b/app/assets/stylesheets/sections/graph.scss
@@ -1,19 +1,38 @@
-.graph_holder {
+.project-network {
border: 1px solid #aaa;
padding: 1px;
-
- h4 {
- padding: 0 10px;
+ .tip {
+ color: #888;
+ font-size: 14px;
+ padding: 10px;
border-bottom: 1px solid #bbb;
@include bg-gray-gradient;
}
- .graph {
+ .network-graph {
background: #f1f1f1;
- cursor: move;
- height: 70%;
- overflow: hidden;
+ height: 500px;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ }
+}
+
+.graphs {
+ .graph-author-commits-count {
+ }
+
+ .graph-author-email {
+ float: right;
+ color: #777;
+ }
+
+ .graph-additions {
+ color: #4a2;
+ }
+
+ .graph-deletions {
+ color: #d12f19;
}
}
diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss
index 5fe18131828..bd72d08295a 100644
--- a/app/assets/stylesheets/sections/header.scss
+++ b/app/assets/stylesheets/sections/header.scss
@@ -5,15 +5,16 @@
header {
&.navbar-gitlab {
.navbar-inner {
- height: 45px;
- padding: 5px;
+ height: 40px;
+ padding: 3px;
background: #F1F1F1;
+ filter: none;
.nav > li > a {
color: $style_color;
text-shadow: 0 1px 0 #fff;
- font-size: 18px;
- padding: 12px;
+ font-size: 14px;
+ padding: 10px;
}
/** NAV block with links and profile **/
@@ -25,7 +26,6 @@ header {
}
z-index: 10;
- /*height: 60px;*/
/**
*
@@ -34,7 +34,7 @@ header {
*/
.app_logo {
float: left;
- margin-right: 15px;
+ margin-right: 9px;
position: relative;
top: -5px;
padding-top: 5px;
@@ -42,10 +42,12 @@ header {
a {
float: left;
padding: 0px;
- margin: 0 10px;
+ margin: 0 6px;
h1 {
- background: url('logo_dark.png') no-repeat 0px 2px;
+ margin: 0;
+ background: url('logo-black.png') no-repeat center 1px;
+ background-size: 38px;
float: left;
height: 40px;
width: 40px;
@@ -67,22 +69,31 @@ header {
position: relative;
float: left;
margin: 0;
- margin-left: 15px;
+ margin-left: 5px;
@include header-font;
}
+ .profile-pic {
+ position: relative;
+ top: -4px;
+ img {
+ width: 26px;
+ height: 26px;
+ @include border-radius(4px);
+ }
+ }
+
/**
*
* Search box
*
*/
.search {
- margin-right: 45px;
+ margin-right: 10px;
margin-left: 10px;
- margin-top: 2px;
.search-input {
- @extend .span2;
+ @extend .span3;
background-image: url("icon-search.png");
background-repeat: no-repeat;
background-position: 10px;
@@ -91,121 +102,13 @@ header {
@include border-radius(3px);
border: 1px solid #c6c6c6;
box-shadow: none;
+ @include transition(all 0.15s ease-in 0s);
&:focus {
- @extend .span3;
+ @extend .span4;
}
}
}
- /**
- *
- * Account box
- *
- */
- .account-box {
- position: absolute;
- right: 0;
- top: 6px;
- z-index: 10000;
- width: 128px;
- font-size: 11px;
- float: right;
- display: block;
- cursor: pointer;
- img {
- @include border-radius(3px);
- right: 5px;
- position: absolute;
- width: 28px;
- height: 28px;
- display: block;
- top: 1px;
- &:after {
- content: " ";
- display: block;
- position: absolute;
- top: 0;
- right: 0;
- left: 0;
- bottom: 0;
- float: right;
- @include border-radius(5px);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-bottom: 0;
- background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255, 255, 255, 0.15)), to(rgba(0, 0, 0, 0.25))),
- -webkit-gradient(linear, left top, right bottom, color-stop(0, rgba(255, 255, 255, 0)), color-stop(0.5, rgba(255, 255, 255, 0.1)), color-stop(0.501, rgba(255, 255, 255, 0)), color-stop(1, rgba(255, 255, 255, 0)));
- background: -moz-linear-gradient(top, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.25)),
- -moz-linear-gradient(left top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0));
- background: linear-gradient(top, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.25)),
- linear-gradient(left top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0));
- -webkit-background-origin: border-box;
- -moz-background-origin: border;
- background-origin: border-box; } } }
-
- .account-box {
- &.hover {
- height: 138px; }
- &:hover > .account-links {
- display: block; } }
-
- .account-links {
- @include border-radius(5px);
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
- position: relative;
- &:before {
- content: ".";
- width: 0;
- height: 0;
- position: absolute;
- border: 5px solid transparent;
- border-color: rgba(255, 255, 255, 0);
- border-bottom-color: #555;
- text-indent: -9999px;
- top: -10px;
- line-height: 0;
- right: 10px;
- z-index: 10; }
- background: #555;
- display: none;
- z-index: 100000;
- @include border-radius(4px);
- width: 130px;
- position: absolute;
- right: 5px;
- top: 38px;
- margin-top: 0;
- float: right;
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
- a {
- color: #fff;
- padding: 12px 15px;
- display: block;
- text-shadow: none;
- border-bottom: 1px solid #666;
- font-size: 12px;
- &:hover {
- color: #fff;
- background: #333;
- }
- }
- }
-
- .account-box.hover .arrow-up {
- top: 41px;
- right: 6px;
- position: absolute; }
-
- .account-links a {
- &:first-child {
- @include border-radius(5px 5px 0 0);
- }
- &:last-child {
- @include border-radius(0 0 5px 5px);
- border-bottom: 0;
- }
- }
-
-
/*
* Dark header
@@ -218,16 +121,25 @@ header {
border-bottom: 1px solid #AAA;
.nav > li > a {
- color: #fff;
- text-shadow: 0 1px 0 #111;
+ color: #AAA;
+ text-shadow: 0 1px 0 #444;
+
+ &:hover {
+ color: #FFF;
+ }
}
}
}
+ .turbolink-spinner {
+ color: #FFF;
+ }
+
.search {
.search-input {
background-color: #D2D5DA;
background-color: rgba(255, 255, 255, 0.5);
+ border: 1px solid #AAA;
&:focus {
background-color: white;
@@ -240,15 +152,22 @@ header {
.app_logo {
a {
h1 {
- background: url('logo_white.png') no-repeat center center;
+ background: url('logo-white.png') no-repeat center 1px;
+ background-size: 38px;
color: #fff;
- text-shadow: 0 1px 1px #111;
+ text-shadow: 0 1px 1px #444;
}
}
}
.project_name {
+ a {
+ color: #BBB;
+ &:hover {
+ color: #FFF;
+ }
+ }
color: #fff;
- text-shadow: 0 1px 1px #111;
+ text-shadow: 0 1px 1px #444;
}
}
@@ -261,11 +180,11 @@ header {
.separator {
float: left;
- height: 60px;
+ height: 46px;
width: 1px;
background: white;
border-left: 1px solid #DDD;
- margin-top: -10px;
+ margin-top: -3px;
margin-left: 10px;
margin-right: 10px;
}
diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss
index 351f2404492..e384aebc76c 100644
--- a/app/assets/stylesheets/sections/issues.scss
+++ b/app/assets/stylesheets/sections/issues.scss
@@ -1,22 +1,39 @@
-.issues_table {
+.issues-list {
.issue {
padding: 10px;
+ position: relative;
- .issue_check {
+ .issue-title {
+ margin-bottom: 5px;
+ font-size: 14px;
+ }
+
+ .issue-info {
+ color: #999;
+ }
+
+ .issue-check {
float: left;
padding: 8px 0;
padding-right: 8px;
min-width: 15px;
}
- p {
- padding-top: 0;
- padding-bottom: 2px;
+ .issue-labels {
+ display: inline-block;
}
- img.avatar {
- width: 32px;
- margin-top: 1px;
+ .issue-actions {
+ display: none;
+ position: absolute;
+ top: 10px;
+ right: 2px;
+ }
+
+ &:hover {
+ .issue-actions {
+ display: block;
+ }
}
}
}
@@ -27,14 +44,17 @@ input.check_all_issues {
margin: 0;
margin-right: 10px;
position: relative;
- top: 8px;
- height: 22px;
+ top: 13px;
}
.issues_content {
.title {
height: 40px;
}
+
+ form {
+ margin: 0;
+ }
}
.btn.close_issue {
@@ -60,7 +80,7 @@ input.check_all_issues {
@media (min-width: 800px) { .issues_bulk_update select { width: 120px; } }
@media (min-width: 1200px) { .issues_bulk_update select { width: 160px; } }
-#issues-table-holder {
+.issues-holder {
.issues_filters {
}
@@ -71,17 +91,7 @@ input.check_all_issues {
}
.update_selected_issues {
- position: relative;
- top:5px;
margin-left: 4px;
- float: left;
- }
-
- .update_issues_text {
- padding: 3px;
- line-height: 28px;
- float: left;
- color: #479;
}
}
}
@@ -89,3 +99,7 @@ input.check_all_issues {
#update_status {
width: 100px;
}
+
+.participants {
+ margin-bottom: 10px;
+}
diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss
index 89b8f1c0055..33bef59c089 100644
--- a/app/assets/stylesheets/sections/login.scss
+++ b/app/assets/stylesheets/sections/login.scss
@@ -1,7 +1,8 @@
/* Login Page */
body.login-page{
- padding-top: 7%;
- background: #666;
+ .container > .content {
+ padding-top: 20px;
+ }
}
.login-box{
@@ -37,3 +38,11 @@ body.login-page{
}
.login-box a.forgot{float: right; padding-top: 6px}
+
+.remember_me {
+ text-align: left;
+
+ input {
+ margin: 2px;
+ }
+}
diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss
index ff715c0fd18..aa61cae4b9a 100644
--- a/app/assets/stylesheets/sections/merge_requests.scss
+++ b/app/assets/stylesheets/sections/merge_requests.scss
@@ -53,57 +53,66 @@
}
}
-li.merge_request {
- padding: 10px;
- img.avatar {
- width: 32px;
- margin-top: 1px;
- }
- p {
- padding: 0px;
- padding-bottom: 2px;
- }
-}
-
.merge-in-progress {
@extend .padded;
@extend .append-bottom-10;
}
-.label_branch {
+.mr_source_commit,
+.mr_target_commit {
+ .commit {
+ margin: 0;
+ padding: 0;
+ padding: 5px 0;
+ list-style: none;
+ &:hover {
+ background: none;
+ }
+ }
+}
+
+.label-branch {
@include border-radius(4px);
- padding: 2px 4px;
+ padding: 3px 4px;
border: none;
font-size: 14px;
background: #474D57;
color: #fff;
font-family: $monospace_font;
+ font-weight: normal;
+ overflow: hidden;
+
+ .label-project {
+ @include border-radius-left(4px);
+ padding: 3px 4px;
+ background: #279;
+ position: relative;
+ left: -4px;
+ letter-spacing: -1px;
+ }
}
-.mr_source_commit,
-.mr_target_commit {
- .commit {
- margin: 0;
- padding: 0;
- padding: 5px;
- margin-bottom: 5px;
- .avatar { position:relative }
- .row_title {
- color: #444;
- }
- .commit-author-name,
- .dash,
- .committed_ago,
- .browse_code_link_holder {
- display: none;
+.mr-list {
+ .merge-request {
+ padding: 10px;
+ position: relative;
+
+ .merge-request-title {
+ margin-bottom: 5px;
+ font-size: 14px;
}
- list-style: none;
- &:hover {
- background: none;
+
+ .merge-request-info {
+ color: #999;
}
}
}
-.mr_direction_tip {
- margin-top:40px
+.merge-request-angle {
+ text-align: center;
+ margin: 0;
+}
+
+.merge-request-form-info {
+ padding: 15px 0;
}
diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss
index 50091cd7365..54263523e85 100644
--- a/app/assets/stylesheets/sections/nav.scss
+++ b/app/assets/stylesheets/sections/nav.scss
@@ -1,68 +1,85 @@
-/*
- * Main Menu of Application
- *
- */
-ul.main_menu {
- margin: auto;
- margin: 30px 0;
- margin-top: 10px;
- height: 38px;
- position: relative;
- overflow: hidden;
- .count {
- position: relative;
- top: -1px;
- display: inline-block;
- height: 15px;
- margin: 0 0 0 5px;
- padding: 0 8px 1px 8px;
- height: auto;
- font-size: 0.82em;
- line-height: 14px;
- text-align: center;
- color: #777;
- }
- .label {
- background: $hover;
- text-shadow: none;
- color: $style_color;
- }
- li {
- list-style-type: none;
- margin: 0;
- display: table-cell;
- width: 1%;
- border-bottom: 2px solid #EEE;
- &.active {
- border-bottom: 2px solid #474D57;
- a {
- color: $style_color;
- }
+.main-nav {
+ background: #f5f5f5;
+ margin: 20px 0;
+ margin-top: 0;
+ padding-top: 4px;
+ border-bottom: 1px solid #E1E1E1;
+
+ ul {
+ margin: auto;
+ height: 40px;
+ overflow: hidden;
+ .count {
+ font-weight: normal;
+ display: inline-block;
+ height: 15px;
+ padding: 1px 6px;
+ height: auto;
+ font-size: 0.82em;
+ line-height: 14px;
+ text-align: center;
+ color: #777;
+ background: #eee;
+ @include border-radius(8px);
}
+ .label {
+ background: $hover;
+ text-shadow: none;
+ color: $style_color;
+ }
+ li {
+ list-style-type: none;
+ margin: 0;
+ display: table-cell;
+ width: 1%;
+ &.active {
+ a {
+ color: $style_color;
+ font-weight: bolder;
+
+ &:after {
+ content: '';
+ display: block;
+ position: relative;
+ bottom: 8px;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border-color: transparent transparent #777 transparent;
+ border-style: solid;
+ border-width: 6px;
+ margin-left: -6px;
+ }
+ }
+ }
+
+ &:hover {
+ a {
+ color: $style_color;
+ }
+ }
- &.home {
- a {
- i {
- font-size: 20px;
- position: relative;
- top: 4px;
+ &.home {
+ a {
+ i {
+ font-size: 20px;
+ position: relative;
+ top: 4px;
+ }
}
}
}
- }
- a {
- display: block;
- text-align: center;
- font-weight: normal;
- height: 36px;
- line-height: 36px;
- color: #777;
- text-shadow: 0 1px 1px white;
- padding: 0 10px;
+ a {
+ display: block;
+ text-align: center;
+ font-weight: normal;
+ height: 38px;
+ line-height: 34px;
+ color: #777;
+ text-shadow: 0 1px 1px white;
+ padding: 0 10px;
+ text-decoration: none;
+ padding-top: 2px;
+ }
}
}
-/*
- * End of Main Menu
- *
- */
-
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss
index 1f92a3a835d..bae1ac3aa9a 100644
--- a/app/assets/stylesheets/sections/notes.scss
+++ b/app/assets/stylesheets/sections/notes.scss
@@ -1,6 +1,13 @@
/**
* Notes
*/
+
+@-webkit-keyframes target-note {
+ from { background:#fffff0; }
+ 50% { background:#ffffd3; }
+ to { background:#fffff0; }
+}
+
ul.notes {
display: block;
list-style: none;
@@ -83,13 +90,23 @@ ul.notes {
margin-top: -20px;
}
.note-body {
+ @include md-typography;
margin-left: 45px;
+
+ .highlight {
+ @include border-radius(4px);
+ }
}
.note-header {
padding-bottom: 5px;
}
}
+ .note:target {
+ -webkit-animation:target-note 2s linear;
+ background: #fffff0;
+ }
+
// paint top or bottom borders depending on notes direction
&:not(.reversed) .note,
&:not(.reversed) .discussion {
@@ -191,7 +208,7 @@ ul.notes {
}
}
- // "show" the icon also if we just hover somwhere over the line
+ // "show" the icon also if we just hover somewhere over the line
&:hover > td {
background: $hover !important;
@@ -212,7 +229,17 @@ ul.notes {
.reply-btn {
@extend .btn-primary;
}
-.file .content tr.line_holder:hover > td { background: $hover !important; }
+.file .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;
+ }
+}
.file .content tr.line_holder:hover > td .line_note_link {
opacity: 1.0;
filter: alpha(opacity=100);
@@ -273,6 +300,15 @@ ul.notes {
}
+.common-note-form {
+ margin: 0;
+ height: 140px;
+ background: #F9F9F9;
+ padding: 3px;
+ padding-bottom: 25px;
+ border: 1px solid #DDD;
+}
+
.note-form-actions {
background: #F9F9F9;
@@ -280,9 +316,41 @@ ul.notes {
padding: 0 5px;
.note-form-option {
- margin-top: 8px;
- margin-left: 15px;
+ margin-top: 10px;
+ margin-left: 30px;
@extend .pull-left;
- @extend .span4;
}
+
+ .js-notify-commit-author {
+ float: left;
+ }
+}
+
+.note-edit-form {
+ display: none;
+
+ .note_text {
+ border: 1px solid #DDD;
+ box-shadow: none;
+ font-size: 14px;
+ height: 80px;
+ width: 98.6%;
+ }
+
+ .form-actions {
+ padding-left: 20px;
+
+ .btn-save {
+ float: left;
+ }
+
+ .note-form-option {
+ float: left;
+ padding: 2px 0 0 25px;
+ }
+ }
+}
+
+.js-note-attachment-delete {
+ display: none;
}
diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss
index 607daf7a97e..5c7516ce6f9 100644
--- a/app/assets/stylesheets/sections/profile.scss
+++ b/app/assets/stylesheets/sections/profile.scss
@@ -1,22 +1,6 @@
-.profile_history {
- .event_feed {
- min-height: 20px;
- .avatar {
- width: 20px;
- }
- }
-}
-
-.profile_avatar_holder {
- float: left;
- width: 60px;
- height: 60px;
- margin-right: 20px;
- img {
- width: 60px;
- height: 60px;
- background: #fff;
- padding: 1px;
- border: 1px solid #ddd;
+.update-notifications {
+ margin-bottom: 0;
+ label {
+ margin-bottom: 0;
}
}
diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss
index 28df1b5ac28..0491b68db57 100644
--- a/app/assets/stylesheets/sections/projects.scss
+++ b/app/assets/stylesheets/sections/projects.scss
@@ -1,56 +1,5 @@
-.projects {
- @extend .row;
- .activities {
- }
-
- .side {
- @extend .pull-right;
-
- .projects_box {
- > .title {
- padding: 2px 15px;
- }
- .nav-projects-tabs li { padding: 0; }
- .well-list {
- li { padding: 15px; }
- .arrow {
- float: right;
- padding: 10px;
- margin: 0;
- }
- .last_activity {
- padding-top: 5px;
- display: block;
- span, strong {
- font-size: 12px;
- color: #666;
- }
- }
- }
- @extend .ui-box;
- }
- }
-}
-
.new_project,
.edit_project {
- .project_name_holder {
- input,
- label {
- font-size: 16px;
- line-height: 20px;
- padding: 8px;
- }
- .btn {
- padding: 6px 10px;
- margin-left: 10px;
- margin-bottom: 8px;
- }
- }
- .adv_settings {
- h6 { margin-left: 40px; }
- }
-
fieldset.features {
.control-label {
font-weight: bold;
@@ -58,28 +7,38 @@
}
}
+.project-name-holder {
+ .help-inline {
+ vertical-align: top;
+ padding: 7px;
+ }
+}
+
.project_clone_panel {
@include border-radius(4px);
@include bg-gray-gradient;
padding: 4px 7px;
border: 1px solid #CCC;
margin-bottom: 20px;
-}
-.project_clone_holder {
- input[type="text"],
.btn {
- font-size: 12px;
- line-height: 18px;
- margin: 0;
- padding: 3px 10px;
+ padding: 4px 12px;
}
+}
+.project_clone_holder {
input[type="text"] {
@extend .monospace;
border: 1px solid #BBB;
box-shadow: none;
margin-left: -1px;
+ background: #FFF;
+ }
+}
+
+.project-public-holder {
+ .help-inline {
+ padding-top: 7px;
}
}
@@ -89,7 +48,7 @@
margin-bottom: 50px;
}
h3 {
- @extend .page_title;
+ @extend .page-title;
}
}
@@ -115,3 +74,44 @@ ul.nav.nav-projects-tabs {
}
}
}
+
+.team_member_row form {
+ margin: 0px;
+}
+
+.my-projects {
+ li {
+ .project-title {
+ font-size: 14px;
+ }
+
+ .project-info {
+ margin-bottom: 10px;
+ }
+
+ .access-icon i {
+ color: #AAA;
+ }
+ }
+}
+
+.public-clone {
+ background: #333;
+ color: #f5f5f5;
+ padding: 6px 10px;
+ margin: 1px;
+ font-weight: normal;
+}
+
+.new-tag-btn {
+ position: relative;
+ top: -5px;
+}
+
+.public-projects .repo-info {
+ color: #777;
+
+ a {
+ color: #777;
+ }
+}
diff --git a/app/assets/stylesheets/sections/snippets.scss b/app/assets/stylesheets/sections/snippets.scss
index 3944814fc3e..e67ca794f25 100644
--- a/app/assets/stylesheets/sections/snippets.scss
+++ b/app/assets/stylesheets/sections/snippets.scss
@@ -1,9 +1,16 @@
-.snippet.file_holder {
- .file_title {
+.snippet.file-holder {
+ .file-title {
.snippet-file-name {
+ padding: 4px 10px;
position: relative;
top: -4px;
left: -4px;
}
}
}
+
+.my-snippets li:first-child {
+ h4 { margin-top: 0; }
+ padding-top: 0;
+}
+
diff --git a/app/assets/stylesheets/sections/stat_graph.scss b/app/assets/stylesheets/sections/stat_graph.scss
new file mode 100644
index 00000000000..b9be47e7700
--- /dev/null
+++ b/app/assets/stylesheets/sections/stat_graph.scss
@@ -0,0 +1,50 @@
+.tint-box {
+ background: #f3f3f3;
+ position: relative;
+ margin-bottom: 10px;
+}
+
+.area {
+ fill: #1db34f;
+ fill-opacity: 0.5;
+}
+
+.axis {
+ fill: #aaa;
+ font-size: 10px;
+}
+
+#contributors {
+ .contributors-list {
+ margin: 0 0 10px 0;
+ list-style: none;
+ padding: 0;
+ }
+
+ .person {
+ &:nth-child(even) {
+ float: right;
+ }
+ float: left;
+ margin-top: 10px;
+ }
+
+ .person .spark {
+ display: block;
+ background: #f3f3f3;
+ }
+
+ .person .area-contributor {
+ fill: #f17f49;
+ }
+}
+
+.selection rect {
+ fill: #333;
+ fill-opacity: 0.1;
+ stroke: #333;
+ stroke-width: 1px;
+ stroke-opacity: 0.4;
+ shape-rendering: crispedges;
+ stroke-dasharray: 3 3;
+}
diff --git a/app/assets/stylesheets/sections/themes.scss b/app/assets/stylesheets/sections/themes.scss
index 4e5eaf575ae..cd1aa2b011a 100644
--- a/app/assets/stylesheets/sections/themes.scss
+++ b/app/assets/stylesheets/sections/themes.scss
@@ -1,10 +1,3 @@
-.application-theme, .code-preview-theme {
- .update-feedback {
- color: #468847;
- float: right;
- }
-}
-
.themes_opts {
padding-left: 20px;
@@ -27,15 +20,15 @@
}
&.modern {
- background: #567;
+ background: #345;
}
&.gray {
- background: #708090;
+ background: #373737;
}
&.violet {
- background: #657;
+ background: #547;
}
}
}
diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss
index 0ba68e5010c..2a84741f0d6 100644
--- a/app/assets/stylesheets/sections/tree.scss
+++ b/app/assets/stylesheets/sections/tree.scss
@@ -13,8 +13,22 @@
}
.tree-table {
+ @extend .table;
@include border-radius(0);
- .tree-item {
+
+ tr {
+ td, th {
+ padding: 8px 10px;
+ line-height: 20px;
+ }
+ th {
+ font-weight: normal;
+ font-size: 15px;
+ border-bottom: 1px solid #CCC;
+ }
+ td {
+ border-color: #F1F1F1;
+ }
&:hover {
td {
background: $hover;
@@ -23,6 +37,13 @@
}
cursor: pointer;
}
+ &.selected {
+ td {
+ background: #f5f5f5;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid #EEE;
+ }
+ }
}
}
@@ -42,17 +63,6 @@
}
}
- .tree-table {
- th .btn {
- margin: -2px -1px;
- padding: 2px 10px;
- }
- td {
- line-height: 20px;
- background: #fafafa;
- }
- }
-
.tree_author {
padding-right: 8px;
@@ -96,9 +106,15 @@
}
.tree-btn-group {
+ top: 2px;
+
.btn {
- margin-right:-3px;
+ margin-right: 0px;
padding: 2px 10px;
}
}
+.tree-ref-holder {
+ float: left;
+ margin-top: 5px;
+}
diff --git a/app/assets/stylesheets/sections/votes.scss b/app/assets/stylesheets/sections/votes.scss
index 4686f5422dc..49489babab7 100644
--- a/app/assets/stylesheets/sections/votes.scss
+++ b/app/assets/stylesheets/sections/votes.scss
@@ -27,7 +27,7 @@
}
}
.votes-block {
- margin: 14px 6px 6px 0;
+ margin: 6px;
.downvotes {
float: right;
}
@@ -35,9 +35,4 @@
.votes-inline {
display: inline-block;
margin: 0 8px;
- .progress {
- display: inline-block;
- padding: 0 0 2px;
- width: 45px;
- }
}
diff --git a/app/assets/stylesheets/sections/wall.scss b/app/assets/stylesheets/sections/wall.scss
new file mode 100644
index 00000000000..d6ac08fcf6f
--- /dev/null
+++ b/app/assets/stylesheets/sections/wall.scss
@@ -0,0 +1,55 @@
+.wall-page {
+ .wall-note-form {
+ @extend .span12;
+
+ margin: 0;
+ height: 140px;
+ background: #F9F9F9;
+ position: fixed;
+ bottom: 0px;
+ padding: 3px;
+ padding-bottom: 25px;
+ border: 1px solid #DDD;
+ }
+
+ .notes {
+ margin-bottom: 160px;
+ background: #FFE;
+ border: 1px solid #EED;
+
+ > li {
+ @extend .clearfix;
+ border-bottom: 1px solid #EED;
+ padding: 10px;
+ }
+
+ .wall-author {
+ color: #666;
+ float: left;
+ font-size: 12px;
+ width: 120px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+ .wall-text {
+ border-left: 1px solid #CCC;
+ margin-left: 10px;
+ padding-left: 10px;
+ float: left;
+ width: 75%;
+ }
+
+ .wall-file {
+ margin-left: 8px;
+ background: #EEE;
+ }
+
+ abbr {
+ float: right;
+ color: #AAA;
+ border: none;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/sections/wiki.scss b/app/assets/stylesheets/sections/wiki.scss
new file mode 100644
index 00000000000..ed3a432ded0
--- /dev/null
+++ b/app/assets/stylesheets/sections/wiki.scss
@@ -0,0 +1,6 @@
+h3.page-title .edit-wiki-header {
+ width: 780px;
+ margin-left: auto;
+ margin-right: auto;
+ padding-right: 7px;
+}
diff --git a/app/assets/stylesheets/selects.scss b/app/assets/stylesheets/selects.scss
new file mode 100644
index 00000000000..09ae57b6692
--- /dev/null
+++ b/app/assets/stylesheets/selects.scss
@@ -0,0 +1,153 @@
+/* CHZN reset few styles */
+.chosen-container-single .chosen-single {
+ background: #FFF;
+ border: 1px solid #bbb;
+ box-shadow: none;
+}
+.chosen-container-active .chosen-single {
+ background: #fff;
+}
+
+.ajax-users-select {
+ width: 400px;
+
+ &.input-large {
+ width: 210px;
+ }
+}
+
+.user-result {
+ .user-image {
+ float: left;
+ }
+ .user-name {
+ }
+ .user-username {
+ color: #999;
+ }
+}
+
+/** Branch/tag selector **/
+.project-refs-form {
+ margin: 0;
+ span {
+ background:none !important;
+ position:static !important;
+ width:auto !important;
+ height:auto !important;
+ }
+}
+.project-refs-select {
+ width: 120px;
+}
+
+.project-refs-form .chosen-container {
+ position: relative;
+ top: 0;
+ left: 0;
+ margin-right: 10px;
+
+ .chosen-drop {
+ min-width: 400px;
+ .chosen-results {
+ max-height: 300px;
+ }
+ .chosen-search input {
+ min-width: 365px;
+ }
+ }
+}
+
+/** Fix for Search Dropdown Border **/
+.chosen-container {
+ min-width: 100px;
+
+ .chosen-search {
+ input:focus {
+ @include box-shadow(none);
+ }
+ }
+
+ .chosen-drop {
+ margin: 7px 0;
+ min-width: 200px;
+ border: 1px solid #bbb;
+ @include border-radius(0);
+
+ .chosen-results {
+ margin-top: 5px;
+ max-height: 300px;
+
+ .group-result {
+ color: $style_color;
+ border-bottom: 1px solid #EEE;
+ padding: 8px;
+ }
+ .active-result {
+ @include border-radius(0);
+
+ &.highlighted {
+ background: $hover;
+ color: $style_color;
+ }
+ &.result-selected {
+ background: #EEE;
+ border-left: 4px solid #CCC;
+ }
+ }
+ }
+
+ .chosen-search {
+ @include bg-gray-gradient;
+ input {
+ min-width: 165px;
+ border-color: #CCC;
+ }
+ }
+ }
+}
+
+.chosen-container .chosen-single,
+.chosen-container.chosen-with-drop .chosen-single {
+ @include bg-light-gray-gradient;
+
+ div {
+ background: transparent;
+ border-left: none;
+ }
+
+ span {
+ font-weight: normal;
+ }
+}
+
+/** Select2 styling **/
+.select2-container .select2-choice {
+ background: #f1f1f1;
+ background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, whitesmoke), to(#e1e1e1));
+ background-image: -webkit-linear-gradient(whitesmoke 6.6%, #e1e1e1);
+ background-image: -moz-linear-gradient(whitesmoke 6.6%, #e1e1e1);
+ background-image: -o-linear-gradient(whitesmoke 6.6%, #e1e1e1);
+}
+
+.select2-container .select2-choice div {
+ border: none;
+ background: none;
+}
+
+.select2-drop {
+ padding-top: 8px;
+}
+
+.select2-no-results, .select2-searching {
+ padding: 7px;
+ color: #666;
+}
+
+.chosen-container .chosen-single div b {
+ background-position-y: 0px !important;
+}
+
+.chosen-container .chosen-drop .chosen-search input {
+ background-position-y: -24px !important;
+}
diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss
index 4e34e8b1b6b..30f0bbaf4c8 100644
--- a/app/assets/stylesheets/themes/ui_basic.scss
+++ b/app/assets/stylesheets/themes/ui_basic.scss
@@ -5,7 +5,11 @@
*/
.ui_basic {
.separator {
- background: white;
+ background: #F9F9F9;
border-left: 1px solid #DDD;
}
+
+ .main-nav {
+ background: #FFF;
+ }
}
diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss
index d7a554ff9e5..1fd54ff18a6 100644
--- a/app/assets/stylesheets/themes/ui_color.scss
+++ b/app/assets/stylesheets/themes/ui_color.scss
@@ -16,15 +16,16 @@
@extend .header-dark;
&.navbar-gitlab {
.navbar-inner {
- background: #657;
+ background: #547;
+ border-bottom: 1px solid #435;
.app_logo {
&:hover {
- background-color: #6A5A7A;
+ background-color: #435;
}
}
.separator {
- background: #546;
- border-left: 1px solid #706080;
+ background: #435;
+ border-left: 1px solid #658;
}
}
}
diff --git a/app/assets/stylesheets/themes/ui_gray.scss b/app/assets/stylesheets/themes/ui_gray.scss
index f0547c72157..41c08c840e2 100644
--- a/app/assets/stylesheets/themes/ui_gray.scss
+++ b/app/assets/stylesheets/themes/ui_gray.scss
@@ -16,15 +16,16 @@
@extend .header-dark;
&.navbar-gitlab {
.navbar-inner {
- background: #708090;
+ background: #373737;
+ border-bottom: 1px solid #272727;
.app_logo {
&:hover {
- background-color: #6A7A8A;
+ background-color: #272727;
}
}
.separator {
- background: #607080;
- border-left: 1px solid #8090A0;
+ background: #272727;
+ border-left: 1px solid #474747;
}
}
}
diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss
index 0a78c5c09f5..a2b8c21ea11 100644
--- a/app/assets/stylesheets/themes/ui_mars.scss
+++ b/app/assets/stylesheets/themes/ui_mars.scss
@@ -8,66 +8,27 @@
*
*/
.ui_mars {
-
/*
* Application Header
*
*/
header {
-
+ @extend .header-dark;
&.navbar-gitlab {
.navbar-inner {
- background: #474D57 url('bg-header.png') repeat-x bottom;
- border-bottom: 1px solid #444;
-
- .nav > li > a {
- color: #eee;
- text-shadow: 0 1px 0 #444;
+ background: #474D57;
+ border-bottom: 1px solid #373D47;
+ .app_logo {
+ &:hover {
+ background-color: #373D47;
+ }
}
}
}
- .search {
- float: right;
- margin-right: 45px;
- .search-input {
- border: 1px solid rgba(0, 0, 0, 0.7);
- background-color: #D2D5DA;
- background-color: rgba(255, 255, 255, 0.5);
-
- &:focus {
- background-color: white;
- }
- }
- }
- .search-input::-webkit-input-placeholder {
- color: #666;
- }
- .app_logo {
- a {
- h1 {
- background: url('logo_white.png') no-repeat center center;
- color: #eee;
- text-shadow: 0 1px 1px #111;
- }
- }
- &:hover {
- background-color: #41464e;
- }
- }
- .project_name {
- color: #eee;
- text-shadow: 0 1px 1px #111;
+ .separator {
+ background: #31363E;
+ border-left: 1px solid #666;
}
}
-
- .separator {
- background: #31363E;
- border-left: 1px solid #666;
- }
-
- /*
- * End of Application Header
- *
- */
}
diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss
index a5bf414c443..6173757082e 100644
--- a/app/assets/stylesheets/themes/ui_modern.scss
+++ b/app/assets/stylesheets/themes/ui_modern.scss
@@ -16,15 +16,16 @@
@extend .header-dark;
&.navbar-gitlab {
.navbar-inner {
- background: #567;
+ background: #345;
+ border-bottom: 1px solid #234;
.app_logo {
&:hover {
- background-color: #516171;
+ background-color: #234;
}
}
.separator {
- background: #456;
- border-left: 1px solid #678;
+ background: #234;
+ border-left: 1px solid #456;
}
}
}
diff --git a/app/contexts/commit_load_context.rb b/app/contexts/commit_load_context.rb
index 1f23f633af3..2930c5b1668 100644
--- a/app/contexts/commit_load_context.rb
+++ b/app/contexts/commit_load_context.rb
@@ -12,7 +12,6 @@ class CommitLoadContext < BaseContext
commit = project.repository.commit(params[:id])
if commit
- commit = CommitDecorator.decorate(commit)
line_notes = project.notes.for_commit_id(commit.id).inline
result[:commit] = commit
@@ -21,7 +20,8 @@ class CommitLoadContext < BaseContext
result[:notes_count] = project.notes.for_commit_id(commit.id).count
begin
- result[:suppress_diff] = true if commit.diffs.size > Commit::DIFF_SAFE_SIZE && !params[:force_show_diff]
+ result[:suppress_diff] = true if commit.diff_suppress? && !params[:force_show_diff]
+ result[:force_suppress_diff] = commit.diff_force_suppress?
rescue Grit::Git::GitTimeout
result[:suppress_diff] = true
result[:status] = :huge_commit
diff --git a/app/contexts/filter_context.rb b/app/contexts/filter_context.rb
index 401d19b31c8..2607b12b4ae 100644
--- a/app/contexts/filter_context.rb
+++ b/app/contexts/filter_context.rb
@@ -11,8 +11,8 @@ class FilterContext
end
def apply_filter items
- if params[:project_id]
- items = items.where(project_id: params[:project_id])
+ if params[:project_id].present?
+ items = items.of_projects(params[:project_id])
end
if params[:search].present?
diff --git a/app/contexts/issues/bulk_update_context.rb b/app/contexts/issues/bulk_update_context.rb
new file mode 100644
index 00000000000..73a3c353523
--- /dev/null
+++ b/app/contexts/issues/bulk_update_context.rb
@@ -0,0 +1,39 @@
+module Issues
+ class BulkUpdateContext < BaseContext
+ def execute
+ update_data = params[:update]
+
+ issues_ids = update_data[:issues_ids].split(",")
+ milestone_id = update_data[:milestone_id]
+ assignee_id = update_data[:assignee_id]
+ status = update_data[:status]
+
+ new_state = nil
+
+ if status.present?
+ if status == 'closed'
+ new_state = :close
+ else
+ new_state = :reopen
+ end
+ end
+
+ opts = {}
+ opts[:milestone_id] = milestone_id if milestone_id.present?
+ opts[:assignee_id] = assignee_id if assignee_id.present?
+
+ issues = Issue.where(id: issues_ids).all
+ issues = issues.select { |issue| can?(current_user, :modify_issue, issue) }
+
+ issues.each do |issue|
+ issue.update_attributes(opts)
+ issue.send new_state if new_state
+ end
+
+ {
+ count: issues.count,
+ success: !issues.count.zero?
+ }
+ end
+ end
+end
diff --git a/app/contexts/issues/list_context.rb b/app/contexts/issues/list_context.rb
new file mode 100644
index 00000000000..da2eed0e259
--- /dev/null
+++ b/app/contexts/issues/list_context.rb
@@ -0,0 +1,36 @@
+module Issues
+ class ListContext < BaseContext
+ attr_accessor :issues
+
+ def execute
+ @issues = @project.issues
+
+ @issues = case params[:state]
+ when 'all' then @issues
+ when 'closed' then @issues.closed
+ else @issues.opened
+ end
+
+ @issues = case params[:scope]
+ when 'assigned-to-me' then @issues.assigned_to(current_user)
+ when 'created-by-me' then @issues.authored(current_user)
+ else @issues
+ end
+
+ @issues = @issues.tagged_with(params[:label_name]) if params[:label_name].present?
+ @issues = @issues.includes(:author, :project)
+
+ # Filter by specific assignee_id (or lack thereof)?
+ if params[:assignee_id].present?
+ @issues = @issues.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id]))
+ end
+
+ # Filter by specific milestone_id (or lack thereof)?
+ if params[:milestone_id].present?
+ @issues = @issues.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id]))
+ end
+
+ @issues
+ end
+ end
+end
diff --git a/app/contexts/issues_bulk_update_context.rb b/app/contexts/issues_bulk_update_context.rb
deleted file mode 100644
index 7c3c1d4f7c3..00000000000
--- a/app/contexts/issues_bulk_update_context.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-class IssuesBulkUpdateContext < BaseContext
- def execute
- update_data = params[:update]
-
- issues_ids = update_data[:issues_ids].split(",")
- milestone_id = update_data[:milestone_id]
- assignee_id = update_data[:assignee_id]
- status = update_data[:status]
-
- opts = {}
- opts[:milestone_id] = milestone_id if milestone_id.present?
- opts[:assignee_id] = assignee_id if assignee_id.present?
- opts[:closed] = (status == "closed") if status.present?
-
- issues = Issue.where(id: issues_ids).all
- issues = issues.select { |issue| can?(current_user, :modify_issue, issue) }
- issues.each { |issue| issue.update_attributes(opts) }
- {
- count: issues.count,
- success: !issues.count.zero?
- }
- end
-end
-
diff --git a/app/contexts/issues_list_context.rb b/app/contexts/issues_list_context.rb
deleted file mode 100644
index 0cc73f99535..00000000000
--- a/app/contexts/issues_list_context.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-class IssuesListContext < BaseContext
- include IssuesHelper
-
- attr_accessor :issues
-
- def execute
- @issues = case params[:status]
- when issues_filter[:all] then @project.issues
- when issues_filter[:closed] then @project.issues.closed
- when issues_filter[:to_me] then @project.issues.opened.assigned(current_user)
- else @project.issues.opened
- end
-
- @issues = @issues.tagged_with(params[:label_name]) if params[:label_name].present?
- @issues = @issues.includes(:author, :project).order("updated_at")
-
- # Filter by specific assignee_id (or lack thereof)?
- if params[:assignee_id].present?
- @issues = @issues.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id]))
- end
-
- # Filter by specific milestone_id (or lack thereof)?
- if params[:milestone_id].present?
- @issues = @issues.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id]))
- end
-
- @issues
- end
-end
diff --git a/app/contexts/merge_requests_load_context.rb b/app/contexts/merge_requests_load_context.rb
index 4ec66cd9b78..c3408db6e11 100644
--- a/app/contexts/merge_requests_load_context.rb
+++ b/app/contexts/merge_requests_load_context.rb
@@ -2,19 +2,23 @@
# based on filtering passed via params for @project
class MergeRequestsLoadContext < BaseContext
def execute
- type = params[:f]
+ merge_requests = @project.merge_requests
- merge_requests = project.merge_requests
-
- merge_requests = case type
+ merge_requests = case params[:state]
when 'all' then merge_requests
when 'closed' then merge_requests.closed
- when 'assigned-to-me' then merge_requests.opened.assigned(current_user)
else merge_requests.opened
end
+ merge_requests = case params[:scope]
+ when 'assigned-to-me' then merge_requests.assigned_to(current_user)
+ when 'created-by-me' then merge_requests.authored(current_user)
+ else merge_requests
+ end
+
+
merge_requests = merge_requests.page(params[:page]).per(20)
- merge_requests = merge_requests.includes(:author, :project).order("closed, created_at desc")
+ merge_requests = merge_requests.includes(:author, :source_project, :target_project).order("created_at desc")
# Filter by specific assignee_id (or lack thereof)?
if params[:assignee_id].present?
diff --git a/app/contexts/notes/create_context.rb b/app/contexts/notes/create_context.rb
index 1367dff4699..36ea76ff949 100644
--- a/app/contexts/notes/create_context.rb
+++ b/app/contexts/notes/create_context.rb
@@ -3,8 +3,6 @@ module Notes
def execute
note = project.notes.new(params[:note])
note.author = current_user
- note.notify = params[:notify].present?
- note.notify_author = params[:notify_author].present?
note.save
note
end
diff --git a/app/contexts/notes/load_context.rb b/app/contexts/notes/load_context.rb
index e3875e1d4e2..234e9ac3cdd 100644
--- a/app/contexts/notes/load_context.rb
+++ b/app/contexts/notes/load_context.rb
@@ -3,8 +3,6 @@ module Notes
def execute
target_type = params[:target_type]
target_id = params[:target_id]
- after_id = params[:after_id]
- before_id = params[:before_id]
@notes = case target_type
@@ -16,17 +14,6 @@ module Notes
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh
when "snippet"
project.snippets.find(target_id).notes.fresh
- when "wall"
- # this is the only case, where the order is DESC
- project.notes.common.inc_author_project.order("created_at DESC, id DESC").limit(50)
- end
-
- @notes = if after_id
- @notes.where("id > ?", after_id)
- elsif before_id
- @notes.where("id < ?", before_id)
- else
- @notes
end
end
end
diff --git a/app/contexts/projects/create_context.rb b/app/contexts/projects/create_context.rb
index 629c5294754..1c60a5de141 100644
--- a/app/contexts/projects/create_context.rb
+++ b/app/contexts/projects/create_context.rb
@@ -8,21 +8,33 @@ module Projects
# get namespace id
namespace_id = params.delete(:namespace_id)
- @project = Project.new(params)
+ # Load default feature settings
+ default_features = Gitlab.config.gitlab.default_projects_features
+
+ default_opts = {
+ issues_enabled: default_features.issues,
+ wiki_enabled: default_features.wiki,
+ wall_enabled: default_features.wall,
+ snippets_enabled: default_features.snippets,
+ merge_requests_enabled: default_features.merge_requests,
+ public: default_features.public
+ }.stringify_keys
+
+ @project = Project.new(default_opts.merge(params))
# Parametrize path for project
#
# Ex.
# 'GitLab HQ'.parameterize => "gitlab-hq"
#
- @project.path = @project.name.dup.parameterize
+ @project.path = @project.name.dup.parameterize unless @project.path.present?
if namespace_id
# Find matching namespace and check if it allowed
# for current user if namespace_id passed.
if allowed_namespace?(current_user, namespace_id)
- @project.namespace_id = namespace_id unless namespace_id == Namespace.global_id
+ @project.namespace_id = namespace_id
else
deny_namespace
return @project
@@ -34,18 +46,15 @@ module Projects
@project.creator = current_user
- # Import project from cloneable resource
- if @project.valid? && @project.import_url.present?
- shell = Gitlab::Shell.new
- if shell.import_repository(@project.path_with_namespace, @project.import_url)
- true
- else
- @project.errors.add(:import_url, 'cannot clone repo')
- end
- end
-
if @project.save
- @project.users_projects.create(project_access: UsersProject::MASTER, user: current_user)
+ @project.discover_default_branch
+
+ unless @project.group
+ @project.users_projects.create(
+ project_access: UsersProject::MASTER,
+ user: current_user
+ )
+ end
end
@project
@@ -61,12 +70,8 @@ module Projects
end
def allowed_namespace?(user, namespace_id)
- if namespace_id == Namespace.global_id
- return user.admin
- else
- namespace = Namespace.find_by_id(namespace_id)
- current_user.can?(:manage_namespace, namespace)
- end
+ namespace = Namespace.find_by_id(namespace_id)
+ current_user.can?(:manage_namespace, namespace)
end
end
end
diff --git a/app/contexts/projects/fork_context.rb b/app/contexts/projects/fork_context.rb
new file mode 100644
index 00000000000..fbc67220d5d
--- /dev/null
+++ b/app/contexts/projects/fork_context.rb
@@ -0,0 +1,44 @@
+module Projects
+ class ForkContext < BaseContext
+ include Gitlab::ShellAdapter
+
+ def initialize(project, user)
+ @from_project, @current_user = project, user
+ end
+
+ def execute
+ project = @from_project.dup
+ project.name = @from_project.name
+ project.path = @from_project.path
+ project.namespace = current_user.namespace
+ project.creator = current_user
+
+ # If the project cannot save, we do not want to trigger the project destroy
+ # as this can have the side effect of deleting a repo attached to an existing
+ # project with the same name and namespace
+ if project.valid?
+ begin
+ Project.transaction do
+ #First save the DB entries as they can be rolled back if the repo fork fails
+ project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id)
+ if project.save
+ project.users_projects.create(project_access: UsersProject::MASTER, user: current_user)
+ end
+ #Now fork the repo
+ unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path)
+ raise "forking failed in gitlab-shell"
+ end
+ project.ensure_satellite_exists
+ end
+ rescue => ex
+ project.errors.add(:base, "Fork transaction failed.")
+ project.destroy
+ end
+ else
+ project.errors.add(:base, "Invalid fork destination")
+ end
+ project
+
+ end
+ end
+end
diff --git a/app/contexts/projects/transfer_context.rb b/app/contexts/projects/transfer_context.rb
new file mode 100644
index 00000000000..3011984e3f8
--- /dev/null
+++ b/app/contexts/projects/transfer_context.rb
@@ -0,0 +1,22 @@
+module Projects
+ class TransferContext < BaseContext
+ def execute(role = :default)
+ namespace_id = params[:project].delete(:namespace_id)
+ allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin
+
+ if allowed_transfer && namespace_id.present?
+ if namespace_id.to_i != project.namespace_id
+ # Transfer to someone namespace
+ namespace = Namespace.find(namespace_id)
+ project.transfer(namespace)
+ end
+ end
+
+ rescue ProjectTransferService::TransferError => ex
+ project.reload
+ project.errors.add(:namespace_id, ex.message)
+ false
+ end
+ end
+end
+
diff --git a/app/contexts/projects/update_context.rb b/app/contexts/projects/update_context.rb
index e5d09b7df7f..40385fa65b0 100644
--- a/app/contexts/projects/update_context.rb
+++ b/app/contexts/projects/update_context.rb
@@ -1,24 +1,8 @@
module Projects
class UpdateContext < BaseContext
def execute(role = :default)
- namespace_id = params[:project].delete(:namespace_id)
+ params[:project].delete(:namespace_id)
params[:project].delete(:public) unless can?(current_user, :change_public_mode, project)
-
- allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin
-
- if allowed_transfer && namespace_id.present?
- if namespace_id == Namespace.global_id
- if project.namespace.present?
- # Transfer to global namespace from anyone
- project.transfer(nil)
- end
- elsif namespace_id.to_i != project.namespace_id
- # Transfer to someone namespace
- namespace = Namespace.find(namespace_id)
- project.transfer(namespace)
- end
- end
-
project.update_attributes(params[:project], as: role)
end
end
diff --git a/app/contexts/search_context.rb b/app/contexts/search_context.rb
index 9becb8d674f..48def0784fd 100644
--- a/app/contexts/search_context.rb
+++ b/app/contexts/search_context.rb
@@ -10,10 +10,19 @@ class SearchContext
return result unless query.present?
- result[:projects] = Project.where(id: project_ids).search(query).limit(10)
- result[:merge_requests] = MergeRequest.where(project_id: project_ids).search(query).limit(10)
- result[:issues] = Issue.where(project_id: project_ids).search(query).limit(10)
- result[:wiki_pages] = Wiki.where(project_id: project_ids).search(query).limit(10)
+ projects = Project.where(id: project_ids)
+ result[:projects] = projects.search(query).limit(20)
+
+ # Search inside single project
+ project = projects.first if projects.length == 1
+
+ if params[:search_code].present?
+ result[:blobs] = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo?
+ else
+ 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[:wiki_pages] = []
+ end
result
end
@@ -22,8 +31,8 @@ class SearchContext
projects: [],
merge_requests: [],
issues: [],
- wiki_pages: []
+ wiki_pages: [],
+ blobs: []
}
end
end
-
diff --git a/app/contexts/test_hook_context.rb b/app/contexts/test_hook_context.rb
index d2d82a52cf5..63eda6c7d06 100644
--- a/app/contexts/test_hook_context.rb
+++ b/app/contexts/test_hook_context.rb
@@ -1,8 +1,7 @@
class TestHookContext < BaseContext
def execute
hook = project.hooks.find(params[:id])
- commits = project.repository.commits(project.default_branch, nil, 3)
- data = project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", current_user)
+ data = GitPushService.new.sample_data(project, current_user)
hook.execute(data)
end
end
diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb
new file mode 100644
index 00000000000..994e707965a
--- /dev/null
+++ b/app/controllers/admin/background_jobs_controller.rb
@@ -0,0 +1,4 @@
+class Admin::BackgroundJobsController < Admin::ApplicationController
+ def show
+ end
+end
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index 3c27b86180b..3c80b6503fa 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -2,8 +2,5 @@ class Admin::DashboardController < Admin::ApplicationController
def index
@projects = Project.order("created_at DESC").limit(10)
@users = User.order("created_at DESC").limit(10)
-
- rescue Redis::InheritedError
- @resque_accessible = false
end
end
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index f552fb595b8..89b395786b3 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -8,12 +8,6 @@ class Admin::GroupsController < Admin::ApplicationController
end
def show
- @projects = Project.scoped
- @projects = @projects.not_in_group(@group) if @group.projects.present?
- @projects = @projects.all
- @projects.reject!(&:empty_repo?)
-
- @users = User.active
end
def new
@@ -26,55 +20,30 @@ class Admin::GroupsController < Admin::ApplicationController
def create
@group = Group.new(params[:group])
@group.path = @group.name.dup.parameterize if @group.name
- @group.owner = current_user
if @group.save
+ @group.add_owner(current_user)
redirect_to [:admin, @group], notice: 'Group was successfully created.'
else
- render action: "new"
+ render "new"
end
end
def update
- group_params = params[:group].dup
- owner_id =group_params.delete(:owner_id)
-
- if owner_id
- @group.owner = User.find(owner_id)
- end
-
- if @group.update_attributes(group_params)
+ if @group.update_attributes(params[:group])
redirect_to [:admin, @group], notice: 'Group was successfully updated.'
else
- render action: "edit"
- end
- end
-
- def project_update
- project_ids = params[:project_ids]
-
- Project.where(id: project_ids).each do |project|
- project.transfer(@group)
+ render "edit"
end
-
- redirect_to :back, notice: 'Group was successfully updated.'
- end
-
- def remove_project
- @project = Project.find(params[:project_id])
- @project.transfer(nil)
-
- redirect_to :back, notice: 'Group was successfully updated.'
end
def project_teams_update
- @group.add_users_to_project_teams(params[:user_ids], params[:project_access])
- redirect_to [:admin, @group], notice: 'Users was successfully added.'
+ @group.add_users(params[:user_ids].split(','), params[:group_access])
+
+ redirect_to [:admin, @group], notice: 'Users were successfully added.'
end
def destroy
- @group.truncate_teams
-
@group.destroy
redirect_to admin_groups_path, notice: 'Group was successfully deleted.'
diff --git a/app/controllers/admin/projects/application_controller.rb b/app/controllers/admin/projects/application_controller.rb
deleted file mode 100644
index b3f1539f387..00000000000
--- a/app/controllers/admin/projects/application_controller.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# Provides a base class for Admin controllers to subclass
-#
-# Automatically sets the layout and ensures an administrator is logged in
-class Admin::Projects::ApplicationController < Admin::ApplicationController
-
- protected
-
- def project
- @project ||= Project.find_with_namespace(params[:project_id])
- end
-end
diff --git a/app/controllers/admin/projects/members_controller.rb b/app/controllers/admin/projects/members_controller.rb
deleted file mode 100644
index d9c0d572bb1..00000000000
--- a/app/controllers/admin/projects/members_controller.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-class Admin::Projects::MembersController < Admin::Projects::ApplicationController
- def edit
- @member = team_member
- @project = project
- @team_member_relation = team_member_relation
- end
-
- def update
- if team_member_relation.update_attributes(params[:team_member])
- redirect_to [:admin, project], notice: 'Project Access was successfully updated.'
- else
- render action: "edit"
- end
- end
-
- def destroy
- team_member_relation.destroy
-
- redirect_to :back
- end
-
- private
-
- def team_member
- @member ||= project.users.find_by_username(params[:id])
- end
-
- def team_member_relation
- team_member.users_projects.find_by_project_id(project)
- end
-
-end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 8ae0bba9a2d..088174fd3b8 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -2,49 +2,20 @@ class Admin::ProjectsController < Admin::ApplicationController
before_filter :project, only: [:edit, :show, :update, :destroy, :team_update]
def index
- @projects = Project.scoped
- @projects = @projects.where(namespace_id: params[:namespace_id]) if params[:namespace_id].present?
+ owner_id = params[:owner_id]
+ user = User.find_by_id(owner_id)
+
+ @projects = user ? user.owned_projects : Project.scoped
@projects = @projects.where(public: true) if params[:public_only].present?
@projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present?
- @projects = @projects.where(namespace_id: nil) if params[:namespace_id] == Namespace.global_id
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
end
def show
@repository = @project.repository
- @users = User.active
- @users = @users.not_in_project(@project) if @project.users.present?
- @users = @users.all
- end
-
- def edit
- end
-
- def team_update
- @project.team.add_users_ids(params[:user_ids], params[:project_access])
-
- redirect_to [:admin, @project], notice: 'Project was successfully updated.'
- end
-
- def update
- project.creator = current_user unless project.creator
-
- status = ::Projects::UpdateContext.new(project, current_user, params).execute(:admin)
-
- if status
- redirect_to [:admin, @project], notice: 'Project was successfully updated.'
- else
- render action: "edit"
- end
- end
-
- def destroy
- @project.team.truncate
- @project.destroy
-
- redirect_to admin_projects_path, notice: 'Project was successfully deleted.'
+ @group = @project.group
end
protected
diff --git a/app/controllers/admin/resque_controller.rb b/app/controllers/admin/resque_controller.rb
deleted file mode 100644
index 7d489ab4876..00000000000
--- a/app/controllers/admin/resque_controller.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-class Admin::ResqueController < Admin::ApplicationController
- def show
- end
-end
diff --git a/app/controllers/admin/teams/application_controller.rb b/app/controllers/admin/teams/application_controller.rb
deleted file mode 100644
index 8710821454e..00000000000
--- a/app/controllers/admin/teams/application_controller.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# Provides a base class for Admin controllers to subclass
-#
-# Automatically sets the layout and ensures an administrator is logged in
-class Admin::Teams::ApplicationController < Admin::ApplicationController
-
- private
-
- def user_team
- @team = UserTeam.find_by_path(params[:team_id])
- end
-end
diff --git a/app/controllers/admin/teams/members_controller.rb b/app/controllers/admin/teams/members_controller.rb
deleted file mode 100644
index e7dbcad568f..00000000000
--- a/app/controllers/admin/teams/members_controller.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-class Admin::Teams::MembersController < Admin::Teams::ApplicationController
- def new
- @users = User.potential_team_members(user_team)
- @users = UserDecorator.decorate @users
- end
-
- def create
- unless params[:user_ids].blank?
- user_ids = params[:user_ids]
- access = params[:default_project_access]
- is_admin = params[:group_admin]
- user_team.add_members(user_ids, access, is_admin)
- end
-
- redirect_to admin_team_path(user_team), notice: 'Members was successfully added into Team of users.'
- end
-
- def edit
- team_member
- end
-
- def update
- options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]}
- if user_team.update_membership(team_member, options)
- redirect_to admin_team_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
- else
- render :edit
- end
- end
-
- def destroy
- user_team.remove_member(team_member)
- redirect_to admin_team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users."
- end
-
- protected
-
- def team_member
- @member ||= user_team.members.find_by_username(params[:id])
- end
-end
diff --git a/app/controllers/admin/teams/projects_controller.rb b/app/controllers/admin/teams/projects_controller.rb
deleted file mode 100644
index 8584a188b20..00000000000
--- a/app/controllers/admin/teams/projects_controller.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-class Admin::Teams::ProjectsController < Admin::Teams::ApplicationController
- def new
- @projects = Project.scoped
- @projects = @projects.without_team(user_team) if user_team.projects.any?
- #@projects.reject!(&:empty_repo?)
- end
-
- def create
- unless params[:project_ids].blank?
- project_ids = params[:project_ids]
- access = params[:greatest_project_access]
- user_team.assign_to_projects(project_ids, access)
- end
-
- redirect_to admin_team_path(user_team), notice: 'Team of users was successfully assgned to projects.'
- end
-
- def edit
- team_project
- end
-
- def update
- if user_team.update_project_access(team_project, params[:greatest_project_access])
- redirect_to admin_team_path(user_team), notice: 'Access was successfully updated.'
- else
- render :edit
- end
- end
-
- def destroy
- user_team.resign_from_project(team_project)
- redirect_to admin_team_path(user_team), notice: 'Team of users was successfully reassigned from project.'
- end
-
- protected
-
- def team_project
- @project ||= user_team.projects.find_with_namespace(params[:id])
- end
-
-end
diff --git a/app/controllers/admin/teams_controller.rb b/app/controllers/admin/teams_controller.rb
deleted file mode 100644
index 786957cbc59..00000000000
--- a/app/controllers/admin/teams_controller.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-class Admin::TeamsController < Admin::ApplicationController
- def index
- @teams = UserTeam.order('name ASC')
- @teams = @teams.search(params[:name]) if params[:name].present?
- @teams = @teams.page(params[:page]).per(20)
- end
-
- def show
- user_team
- end
-
- def new
- @team = UserTeam.new
- end
-
- def edit
- user_team
- end
-
- def create
- @team = UserTeam.new(params[:user_team])
- @team.path = @team.name.dup.parameterize if @team.name
- @team.owner = current_user
-
- if @team.save
- redirect_to admin_team_path(@team), notice: 'Team of users was successfully created.'
- else
- render action: "new"
- end
- end
-
- def update
- user_team_params = params[:user_team].dup
- owner_id = user_team_params.delete(:owner_id)
-
- if owner_id
- user_team.owner = User.find(owner_id)
- end
-
- if user_team.update_attributes(user_team_params)
- redirect_to admin_team_path(user_team), notice: 'Team of users was successfully updated.'
- else
- render action: "edit"
- end
- end
-
- def destroy
- user_team.destroy
-
- redirect_to admin_teams_path, notice: 'Team of users was successfully deleted.'
- end
-
- protected
-
- def user_team
- @team ||= UserTeam.find_by_path(params[:id])
- end
-
-end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 2e7114e10a9..70bbe306562 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -1,70 +1,60 @@
class Admin::UsersController < Admin::ApplicationController
- before_filter :admin_user, only: [:show, :edit, :update, :destroy]
+ before_filter :user, only: [:show, :edit, :update, :destroy]
def index
- @admin_users = User.scoped
- @admin_users = @admin_users.filter(params[:filter])
- @admin_users = @admin_users.search(params[:name]) if params[:name].present?
- @admin_users = @admin_users.alphabetically.page(params[:page])
+ @users = User.scoped
+ @users = @users.filter(params[:filter])
+ @users = @users.search(params[:name]) if params[:name].present?
+ @users = @users.alphabetically.page(params[:page])
end
def show
- # Projects user can be added to
- @not_in_projects = Project.scoped
- @not_in_projects = @not_in_projects.without_user(admin_user) if admin_user.authorized_projects.present?
-
- # Projects he already own or joined
- @projects = admin_user.authorized_projects.where('projects.id in (?)', admin_user.authorized_projects.map(&:id))
- end
-
- def team_update
- UsersProject.add_users_into_projects(
- params[:project_ids],
- [admin_user.id],
- params[:project_access]
- )
-
- redirect_to [:admin, admin_user], notice: 'Teams were successfully updated.'
+ @projects = user.authorized_projects
end
-
def new
- @admin_user = User.new({ projects_limit: Gitlab.config.gitlab.default_projects_limit }, as: :admin)
+ @user = User.build_user
end
def edit
- admin_user
+ user
end
def block
- if admin_user.block
+ if user.block
redirect_to :back, alert: "Successfully blocked"
else
- redirect_to :back, alert: "Error occured. User was not blocked"
+ redirect_to :back, alert: "Error occurred. User was not blocked"
end
end
def unblock
- if admin_user.update_attribute(:blocked, false)
+ if user.activate
redirect_to :back, alert: "Successfully unblocked"
else
- redirect_to :back, alert: "Error occured. User was not unblocked"
+ redirect_to :back, alert: "Error occurred. User was not unblocked"
end
end
def create
admin = params[:user].delete("admin")
- @admin_user = User.new(params[:user], as: :admin)
- @admin_user.admin = (admin && admin.to_i > 0)
+ opts = {
+ force_random_password: true,
+ password_expires_at: Time.now
+ }
+
+ @user = User.build_user(params[:user].merge(opts), as: :admin)
+ @user.admin = (admin && admin.to_i > 0)
+ @user.created_by_id = current_user.id
respond_to do |format|
- if @admin_user.save
- format.html { redirect_to [:admin, @admin_user], notice: 'User was successfully created.' }
- format.json { render json: @admin_user, status: :created, location: @admin_user }
+ if @user.save
+ format.html { redirect_to [:admin, @user], notice: 'User was successfully created.' }
+ format.json { render json: @user, status: :created, location: @user }
else
- format.html { render action: "new" }
- format.json { render json: @admin_user.errors, status: :unprocessable_entity }
+ format.html { render "new" }
+ format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
@@ -77,24 +67,27 @@ class Admin::UsersController < Admin::ApplicationController
params[:user].delete(:password_confirmation)
end
- admin_user.admin = (admin && admin.to_i > 0)
+ user.admin = (admin && admin.to_i > 0)
respond_to do |format|
- if admin_user.update_attributes(params[:user], as: :admin)
- format.html { redirect_to [:admin, admin_user], notice: 'User was successfully updated.' }
+ if user.update_attributes(params[:user], as: :admin)
+ format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' }
format.json { head :ok }
else
- format.html { render action: "edit" }
- format.json { render json: admin_user.errors, status: :unprocessable_entity }
+ # restore username to keep form action url.
+ user.username = params[:id]
+ format.html { render "edit" }
+ format.json { render json: user.errors, status: :unprocessable_entity }
end
end
end
def destroy
- if admin_user.personal_projects.count > 0
- redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return
- end
- admin_user.destroy
+ # 1. Remove groups where user is the only owner
+ user.solo_owned_groups.map(&:destroy)
+
+ # 2. Remove user with all authored content including personal projects
+ user.destroy
respond_to do |format|
format.html { redirect_to admin_users_path }
@@ -104,7 +97,7 @@ class Admin::UsersController < Admin::ApplicationController
protected
- def admin_user
- @admin_user ||= User.find_by_username!(params[:id])
+ def user
+ @user ||= User.find_by_username!(params[:id])
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1f211bac9c2..d974600dcc1 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,10 +1,12 @@
class ApplicationController < ActionController::Base
before_filter :authenticate_user!
before_filter :reject_blocked!
- before_filter :set_current_user_for_observers
+ before_filter :check_password_expiration
+ before_filter :set_current_user_for_thread
before_filter :add_abilities
before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers
+ before_filter :add_gon_variables
protect_from_forgery
@@ -29,26 +31,25 @@ class ApplicationController < ActionController::Base
end
def reject_blocked!
- if current_user && current_user.blocked
+ if current_user && current_user.blocked?
sign_out current_user
- flash[:alert] = "Your account is blocked. Retry when an admin unblock it."
+ flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
redirect_to new_user_session_path
end
end
def after_sign_in_path_for resource
- if resource.is_a?(User) && resource.respond_to?(:blocked) && resource.blocked
+ if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked?
sign_out resource
- flash[:alert] = "Your account is blocked. Retry when an admin unblock it."
+ flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
new_user_session_path
else
super
end
end
- def set_current_user_for_observers
- MergeRequestObserver.current_user = current_user
- IssueObserver.current_user = current_user
+ def set_current_user_for_thread
+ Thread.current[:current_user] = current_user
end
def abilities
@@ -68,7 +69,7 @@ class ApplicationController < ActionController::Base
@project
else
@project = nil
- render_404
+ render_404 and return
end
end
@@ -87,19 +88,11 @@ class ApplicationController < ActionController::Base
end
def authorize_code_access!
- return access_denied! unless can?(current_user, :download_code, project)
+ return access_denied! unless can?(current_user, :download_code, project) or project.public?
end
- def authorize_create_team!
- return access_denied! unless can?(current_user, :create_team, nil)
- end
-
- def authorize_manage_user_team!
- return access_denied! unless user_team.present? && can?(current_user, :manage_user_team, user_team)
- end
-
- def authorize_admin_user_team!
- return access_denied! unless user_team.present? && can?(current_user, :admin_user_team, user_team)
+ def authorize_push!
+ return access_denied! unless can?(current_user, :push_code, project)
end
def access_denied!
@@ -148,4 +141,23 @@ class ApplicationController < ActionController::Base
headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block'
end
+
+ def add_gon_variables
+ gon.default_issues_tracker = Project.issues_tracker.default_value
+ gon.api_version = API::API.version
+ gon.api_token = current_user.private_token if current_user
+ gon.gravatar_url = request.ssl? || Gitlab.config.gitlab.https ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
+ gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
+ end
+
+ def check_password_expiration
+ if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
+ redirect_to new_profile_password_path and return
+ end
+ end
+
+ def event_filter
+ filters = cookies['event_filter'].split(',') if cookies['event_filter'].present?
+ @event_filter ||= EventFilter.new(filters)
+ end
end
diff --git a/app/controllers/blob_controller.rb b/app/controllers/blob_controller.rb
deleted file mode 100644
index d4a45d9508e..00000000000
--- a/app/controllers/blob_controller.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# Controller for viewing a file's blame
-class BlobController < ProjectResourceController
- include ExtractsPath
-
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
- before_filter :require_non_empty_project
-
- before_filter :assign_ref_vars
-
- def show
- if @tree.is_blob?
- send_data(
- @tree.data,
- type: @tree.mime_type,
- disposition: 'inline',
- filename: @tree.name
- )
- else
- not_found!
- end
- end
-end
diff --git a/app/controllers/compare_controller.rb b/app/controllers/compare_controller.rb
deleted file mode 100644
index ae20f9c0ba6..00000000000
--- a/app/controllers/compare_controller.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-class CompareController < ProjectResourceController
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
- before_filter :require_non_empty_project
-
- def index
- end
-
- def show
- result = Commit.compare(project, params[:from], params[:to])
-
- @commits = result[:commits]
- @commit = result[:commit]
- @diffs = result[:diffs]
- @refs_are_same = result[:same]
- @line_notes = []
-
- @commits = CommitDecorator.decorate(@commits)
- end
-
- def create
- redirect_to project_compare_path(@project, params[:from], params[:to])
- end
-end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index f320e819e26..ac319384434 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -1,15 +1,18 @@
class DashboardController < ApplicationController
respond_to :html
- before_filter :load_projects
+ before_filter :load_projects, except: [:projects]
before_filter :event_filter, only: :show
def show
- @groups = current_user.authorized_groups
+ # Fetch only 30 projects.
+ # If user needs more - point to Dashboard#projects page
+ @projects_limit = 30
+
+ @groups = current_user.authorized_groups.sort_by(&:human_name)
@has_authorized_projects = @projects.count > 0
- @teams = current_user.authorized_teams
@projects_count = @projects.count
- @projects = @projects.limit(20)
+ @projects = @projects.limit(@projects_limit)
@events = Event.in_projects(current_user.authorized_projects.pluck(:id))
@events = @event_filter.apply_filter(@events)
@@ -27,13 +30,22 @@ class DashboardController < ApplicationController
def projects
@projects = case params[:scope]
when 'personal' then
- @projects.personal(current_user)
+ current_user.namespace.projects
when 'joined' then
- @projects.joined(current_user)
+ current_user.authorized_projects.joined(current_user)
+ when 'owned' then
+ current_user.owned_projects
else
- @projects
+ current_user.authorized_projects
end
+ @projects = @projects.where(namespace_id: Group.find_by_name(params[:group])) if params[:group].present?
+ @projects = @projects.includes(:namespace).sorted_by_activity
+
+ @labels = current_user.authorized_projects.tags_on(:labels)
+ @groups = current_user.authorized_groups
+
+ @projects = @projects.tagged_with(params[:label]) if params[:label].present?
@projects = @projects.page(params[:page]).per(30)
end
@@ -62,9 +74,4 @@ class DashboardController < ApplicationController
def load_projects
@projects = current_user.authorized_projects.sorted_by_activity
end
-
- def event_filter
- filters = cookies['event_filter'].split(',') if cookies['event_filter']
- @event_filter ||= EventFilter.new(filters)
- end
end
diff --git a/app/controllers/deploy_keys_controller.rb b/app/controllers/deploy_keys_controller.rb
deleted file mode 100644
index a89ebbcb8d5..00000000000
--- a/app/controllers/deploy_keys_controller.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-class DeployKeysController < ProjectResourceController
- respond_to :html
-
- # Authorize
- before_filter :authorize_admin_project!
-
- def index
- @keys = @project.deploy_keys.all
- end
-
- def show
- @key = @project.deploy_keys.find(params[:id])
- end
-
- def new
- @key = @project.deploy_keys.new
-
- respond_with(@key)
- end
-
- def create
- @key = @project.deploy_keys.new(params[:key])
- if @key.save
- redirect_to project_deploy_keys_path(@project)
- else
- render "new"
- end
- end
-
- def destroy
- @key = @project.deploy_keys.find(params[:id])
- @key.destroy
-
- respond_to do |format|
- format.html { redirect_to project_deploy_keys_url }
- format.js { render nothing: true }
- end
- end
-end
diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb
new file mode 100644
index 00000000000..bf30de565ed
--- /dev/null
+++ b/app/controllers/files_controller.rb
@@ -0,0 +1,17 @@
+class FilesController < ApplicationController
+ def download
+ note = Note.find(params[:id])
+ uploader = note.attachment
+
+ if uploader.file_storage?
+ if can?(current_user, :read_project, note.project)
+ send_file uploader.file.path, disposition: 'attachment'
+ else
+ not_found!
+ end
+ else
+ redirect_to uploader.url
+ end
+ end
+end
+
diff --git a/app/controllers/graph_controller.rb b/app/controllers/graph_controller.rb
deleted file mode 100644
index c370433e500..00000000000
--- a/app/controllers/graph_controller.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-class GraphController < ProjectResourceController
- include ExtractsPath
-
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
- before_filter :require_non_empty_project
-
- def show
- if params.has_key?(:q) && params[:q].blank?
- redirect_to project_graph_path(@project, params[:id])
- return
- end
-
- if params.has_key?(:q)
- @q = params[:q]
- @commit = @project.repository.commit(@q) || @commit
- end
-
- respond_to do |format|
- format.html
- format.json do
- graph = Gitlab::Graph::JsonBuilder.new(project, @ref, @commit)
- render :json => graph.to_json
- end
- end
- end
-end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 7b8649a6bdf..f80167da4cb 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -1,7 +1,5 @@
class GroupsController < ApplicationController
respond_to :html
- layout 'group', except: [:new, :create]
-
before_filter :group, except: [:new, :create]
# Authorize
@@ -12,6 +10,10 @@ class GroupsController < ApplicationController
# Load group projects
before_filter :projects, except: [:new, :create]
+ layout :determine_layout
+
+ before_filter :set_title, only: [:new, :create]
+
def new
@group = Group.new
end
@@ -19,9 +21,9 @@ class GroupsController < ApplicationController
def create
@group = Group.new(params[:group])
@group.path = @group.name.dup.parameterize if @group.name
- @group.owner = current_user
if @group.save
+ @group.add_owner(current_user)
redirect_to @group, notice: 'Group was successfully created.'
else
render action: "new"
@@ -29,7 +31,9 @@ class GroupsController < ApplicationController
end
def show
- @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0)
+ @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
respond_to do |format|
@@ -59,44 +63,17 @@ class GroupsController < ApplicationController
end
end
- def search
- result = SearchContext.new(project_ids, params).execute
-
- @projects = result[:projects]
- @merge_requests = result[:merge_requests]
- @issues = result[:issues]
- @wiki_pages = result[:wiki_pages]
- end
-
- def people
+ def members
@project = group.projects.find(params[:project_id]) if params[:project_id]
- @users = @project ? @project.users : group.users
- @users.sort_by!(&:name)
-
- if @project
- @team_member = @project.users_projects.new
- else
- @team_member = UsersProject.new
- end
- end
-
- def team_members
- @group.add_users_to_project_teams(params[:user_ids], params[:project_access])
- redirect_to people_group_path(@group), notice: 'Users was successfully added.'
+ @members = group.users_groups.order('group_access DESC')
+ @users_group = UsersGroup.new
end
def edit
end
def update
- group_params = params[:group].dup
- owner_id =group_params.delete(:owner_id)
-
- if owner_id
- @group.owner = User.find(owner_id)
- end
-
- if @group.update_attributes(group_params)
+ if @group.update_attributes(params[:group])
redirect_to @group, notice: 'Group was successfully updated.'
else
render action: "edit"
@@ -104,7 +81,6 @@ class GroupsController < ApplicationController
end
def destroy
- @group.truncate_teams
@group.destroy
redirect_to root_path, notice: 'Group was removed.'
@@ -126,7 +102,7 @@ class GroupsController < ApplicationController
# Dont allow unauthorized access to group
def authorize_read_group!
- unless projects.present? or can?(current_user, :manage_group, @group)
+ unless projects.present? or can?(current_user, :read_group, @group)
return render_404
end
end
@@ -142,4 +118,16 @@ class GroupsController < ApplicationController
return render_404
end
end
+
+ def set_title
+ @title = 'New Group'
+ end
+
+ def determine_layout
+ if [:new, :create].include?(action_name.to_sym)
+ 'navless'
+ else
+ 'group'
+ end
+ end
end
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index b22280d2fd2..051cbdfaf05 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -1,4 +1,18 @@
class HelpController < ApplicationController
def index
end
+
+ def api
+ @category = params[:category]
+ @category = "README" if @category.blank?
+
+ if File.exists?(Rails.root.join('doc', 'api', @category + '.md'))
+ render 'api'
+ else
+ not_found!
+ end
+ end
+
+ def shortcuts
+ end
end
diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb
deleted file mode 100644
index ab6bf595982..00000000000
--- a/app/controllers/merge_requests_controller.rb
+++ /dev/null
@@ -1,145 +0,0 @@
-class MergeRequestsController < ProjectResourceController
- before_filter :module_enabled
- before_filter :merge_request, only: [:edit, :update, :show, :commits, :diffs, :automerge, :automerge_check, :ci_status]
- before_filter :validates_merge_request, only: [:show, :diffs]
- before_filter :define_show_vars, only: [:show, :diffs]
-
- # Allow read any merge_request
- before_filter :authorize_read_merge_request!
-
- # Allow write(create) merge_request
- before_filter :authorize_write_merge_request!, only: [:new, :create]
-
- # Allow modify merge_request
- before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
-
- def index
- @merge_requests = MergeRequestsLoadContext.new(project, current_user, params).execute
- end
-
- def show
- @target_type = :merge_request
- @target_id = @merge_request.id
-
- respond_to do |format|
- format.html
- format.js
-
- format.diff { render text: @merge_request.to_diff }
- format.patch { render text: @merge_request.to_patch }
- end
- end
-
- def diffs
- @diffs = @merge_request.diffs
- @commit = @merge_request.last_commit
-
- @comments_allowed = @reply_allowed = true
- @comments_target = { noteable_type: 'MergeRequest',
- noteable_id: @merge_request.id }
- @line_notes = @merge_request.notes.where("line_code is not null")
- end
-
- def new
- @merge_request = @project.merge_requests.new(params[:merge_request])
- end
-
- def edit
- end
-
- def create
- @merge_request = @project.merge_requests.new(params[:merge_request])
- @merge_request.author = current_user
-
- if @merge_request.save
- @merge_request.reload_code
- redirect_to [@project, @merge_request], notice: 'Merge request was successfully created.'
- else
- render action: "new"
- end
- end
-
- def update
- 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
- redirect_to [@project, @merge_request], notice: 'Merge request was successfully updated.'
- else
- render action: "edit"
- end
- end
-
- def automerge_check
- if @merge_request.unchecked?
- @merge_request.check_if_can_be_merged
- end
- render json: {state: @merge_request.human_state}
- rescue Gitlab::SatelliteNotExistError
- render json: {state: :no_satellite}
- end
-
- def automerge
- return access_denied! unless can?(current_user, :accept_mr, @project)
- 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)
- @status = true
- else
- @status = false
- end
- end
-
- def branch_from
- @commit = @repository.commit(params[:ref])
- @commit = CommitDecorator.decorate(@commit)
- end
-
- def branch_to
- @commit = @repository.commit(params[:ref])
- @commit = CommitDecorator.decorate(@commit)
- end
-
- def ci_status
- status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
- response = { status: status }
-
- render json: response
- end
-
- protected
-
- def merge_request
- @merge_request ||= @project.merge_requests.find(params[:id])
- end
-
- def authorize_modify_merge_request!
- return render_404 unless can?(current_user, :modify_merge_request, @merge_request)
- end
-
- def authorize_admin_merge_request!
- return render_404 unless can?(current_user, :admin_merge_request, @merge_request)
- end
-
- def module_enabled
- return render_404 unless @project.merge_requests_enabled
- end
-
- def validates_merge_request
- # Show git not found page if target branch doesnt exist
- return git_not_found! unless @project.repo.heads.map(&:name).include?(@merge_request.target_branch)
-
- # Show git not found page if source branch doesnt exist
- # and there is no saved commits between source & target branch
- return git_not_found! if !@project.repo.heads.map(&:name).include?(@merge_request.source_branch) && @merge_request.commits.blank?
- end
-
- def define_show_vars
- # Build a note object for comment form
- @note = @project.notes.new(noteable: @merge_request)
-
- # Get commits from repository
- # or from cache if already merged
- @commits = @merge_request.commits
- @commits = CommitDecorator.decorate(@commits)
- end
-end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index c4ebf0e4889..7131e0fe181 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -16,35 +16,41 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
def ldap
- # We only find ourselves here if the authentication to LDAP was successful.
- @user = User.find_for_ldap_auth(request.env["omniauth.auth"], current_user)
- if @user.persisted?
- @user.remember_me = true
- end
- sign_in_and_redirect @user
+ # We only find ourselves here
+ # if the authentication to LDAP was successful.
+ @user = Gitlab::LDAP::User.find_or_create(oauth)
+ @user.remember_me = true if @user.persisted?
+ sign_in_and_redirect(@user)
end
private
def handle_omniauth
- oauth = request.env['omniauth.auth']
- provider, uid = oauth['provider'], oauth['uid']
-
if current_user
# Change a logged-in user's authentication method:
- current_user.extern_uid = uid
- current_user.provider = provider
+ current_user.extern_uid = oauth['uid']
+ current_user.provider = oauth['provider']
current_user.save
redirect_to profile_path
else
- @user = User.find_or_new_for_omniauth(oauth)
+ @user = Gitlab::OAuth::User.find(oauth)
+
+ # Create user if does not exist
+ # and allow_single_sign_on is true
+ if Gitlab.config.omniauth['allow_single_sign_on']
+ @user ||= Gitlab::OAuth::User.create(oauth)
+ end
if @user
- sign_in_and_redirect @user
+ sign_in_and_redirect(@user)
else
flash[:notice] = "There's no such user!"
redirect_to new_user_session_path
end
end
end
+
+ def oauth
+ @oauth ||= request.env['omniauth.auth']
+ end
end
diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/profiles/groups_controller.rb
new file mode 100644
index 00000000000..378ff6bcf34
--- /dev/null
+++ b/app/controllers/profiles/groups_controller.rb
@@ -0,0 +1,24 @@
+class Profiles::GroupsController < ApplicationController
+ layout "profile"
+
+ def index
+ @user_groups = current_user.users_groups.page(params[:page]).per(20)
+ end
+
+ def leave
+ @users_group = group.users_groups.where(user_id: current_user.id).first
+
+ if group.last_owner?(current_user)
+ redirect_to(profile_groups_path, alert: "You can't leave group. You must add at least one more owner to it.")
+ else
+ @users_group.destroy
+ redirect_to(profile_groups_path, info: "You left #{group.name} group.")
+ end
+ end
+
+ private
+
+ def group
+ @group ||= Group.find_by_path(params[:id])
+ end
+end
diff --git a/app/controllers/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 1a25d834e12..c36dae2abd3 100644
--- a/app/controllers/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -1,9 +1,8 @@
-class KeysController < ApplicationController
+class Profiles::KeysController < ApplicationController
layout "profile"
- respond_to :js, :html
def index
- @keys = current_user.keys.all
+ @keys = current_user.keys.order('id DESC').all
end
def show
@@ -12,15 +11,16 @@ class KeysController < ApplicationController
def new
@key = current_user.keys.new
-
- respond_with(@key)
end
def create
@key = current_user.keys.new(params[:key])
- @key.save
- respond_with(@key)
+ if @key.save
+ redirect_to profile_key_path(@key)
+ else
+ render 'new'
+ end
end
def destroy
@@ -28,7 +28,7 @@ class KeysController < ApplicationController
@key.destroy
respond_to do |format|
- format.html { redirect_to keys_url }
+ format.html { redirect_to profile_keys_url }
format.js { render nothing: true }
end
end
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
new file mode 100644
index 00000000000..5c492aeb49d
--- /dev/null
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -0,0 +1,26 @@
+class Profiles::NotificationsController < ApplicationController
+ layout 'profile'
+
+ def show
+ @notification = current_user.notification
+ @users_projects = current_user.users_projects
+ @users_groups = current_user.users_groups
+ end
+
+ def update
+ type = params[:notification_type]
+
+ @saved = if type == 'global'
+ current_user.notification_level = params[:notification_level]
+ current_user.save
+ elsif type == 'group'
+ users_group = current_user.users_groups.find(params[:notification_id])
+ users_group.notification_level = params[:notification_level]
+ users_group.save
+ else
+ users_project = current_user.users_projects.find(params[:notification_id])
+ users_project.notification_level = params[:notification_level]
+ users_project.save
+ end
+ end
+end
diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb
new file mode 100644
index 00000000000..432899f857d
--- /dev/null
+++ b/app/controllers/profiles/passwords_controller.rb
@@ -0,0 +1,38 @@
+class Profiles::PasswordsController < ApplicationController
+ layout 'navless'
+
+ skip_before_filter :check_password_expiration
+
+ before_filter :set_user
+ before_filter :set_title
+
+ def new
+ end
+
+ def create
+ new_password = params[:user][:password]
+ new_password_confirmation = params[:user][:password_confirmation]
+
+ result = @user.update_attributes(
+ password: new_password,
+ password_confirmation: new_password_confirmation
+ )
+
+ if result
+ @user.update_attributes(password_expires_at: nil)
+ redirect_to root_path, notice: 'Password successfully changed'
+ else
+ render :new
+ end
+ end
+
+ private
+
+ def set_user
+ @user = current_user
+ end
+
+ def set_title
+ @title = "New password"
+ end
+end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 1d1efb16f04..75f12f8a6af 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -1,5 +1,10 @@
class ProfilesController < ApplicationController
+ include ActionView::Helpers::SanitizeHelper
+
before_filter :user
+ before_filter :authorize_change_password!, only: :update_password
+ before_filter :authorize_change_username!, only: :update_username
+
layout 'profile'
def show
@@ -28,9 +33,16 @@ class ProfilesController < ApplicationController
end
def update_password
- params[:user].reject!{ |k, v| k != "password" && k != "password_confirmation"}
+ password_attributes = params[:user].select do |key, value|
+ %w(password password_confirmation).include?(key.to_s)
+ end
- if @user.update_attributes(params[:user])
+ unless @user.valid_password?(params[:user][:current_password])
+ redirect_to account_profile_path, alert: 'You must provide a valid current password'
+ return
+ end
+
+ if @user.update_attributes(password_attributes)
flash[:notice] = "Password was successfully updated. Please login with it"
redirect_to new_user_session_path
else
@@ -63,4 +75,12 @@ class ProfilesController < ApplicationController
def user
@user = current_user
end
+
+ def authorize_change_password!
+ return render_404 if @user.ldap_user?
+ end
+
+ def authorize_change_username!
+ return render_404 unless @user.can_change_username?
+ end
end
diff --git a/app/controllers/project_resource_controller.rb b/app/controllers/project_resource_controller.rb
deleted file mode 100644
index ea78b3ff7c4..00000000000
--- a/app/controllers/project_resource_controller.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-class ProjectResourceController < ApplicationController
- before_filter :project
- before_filter :repository
-end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 7e4776d2d75..8fd4565f367 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -1,11 +1,26 @@
class Projects::ApplicationController < ApplicationController
+ before_filter :project
+ before_filter :repository
+ layout :determine_layout
- before_filter :authorize_admin_team_member!
+ def authenticate_user!
+ # Restrict access to Projects area only
+ # for non-signed users
+ if !current_user
+ id = params[:project_id] || params[:id]
+ @project = Project.find_with_namespace(id)
- protected
+ return if @project && @project.public
+ end
- def user_team
- @team ||= UserTeam.find_by_path(params[:id])
+ super
end
+ def determine_layout
+ if current_user
+ 'projects'
+ else
+ 'public_projects'
+ end
+ end
end
diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb
new file mode 100644
index 00000000000..e58b4507202
--- /dev/null
+++ b/app/controllers/projects/blame_controller.rb
@@ -0,0 +1,14 @@
+# Controller for viewing a file's blame
+class Projects::BlameController < Projects::ApplicationController
+ include ExtractsPath
+
+ # Authorize
+ before_filter :authorize_read_project!
+ before_filter :authorize_code_access!
+ before_filter :require_non_empty_project
+
+ def show
+ @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path)
+ @blame = Gitlab::Git::Blame.new(project.repository, @commit.id, @path)
+ end
+end
diff --git a/app/controllers/blame_controller.rb b/app/controllers/projects/blob_controller.rb
index 37d7245ccb4..b1329c01ce7 100644
--- a/app/controllers/blame_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -1,5 +1,5 @@
# Controller for viewing a file's blame
-class BlameController < ProjectResourceController
+class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
# Authorize
@@ -7,10 +7,7 @@ class BlameController < ProjectResourceController
before_filter :authorize_code_access!
before_filter :require_non_empty_project
- before_filter :assign_ref_vars
-
def show
- @repo = @project.repo
- @blame = Grit::Blob.blame(@repo, @commit.id, @path)
+ @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path)
end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
new file mode 100644
index 00000000000..aa6914414ce
--- /dev/null
+++ b/app/controllers/projects/branches_controller.rb
@@ -0,0 +1,40 @@
+class Projects::BranchesController < Projects::ApplicationController
+ # Authorize
+ before_filter :authorize_read_project!
+ before_filter :require_non_empty_project
+
+ before_filter :authorize_code_access!
+ before_filter :authorize_push!, only: [:create]
+ before_filter :authorize_admin_project!, only: [:destroy]
+
+ def index
+ @branches = Kaminari.paginate_array(@repository.branches).page(params[:page]).per(30)
+ end
+
+ def recent
+ @branches = @repository.recent_branches
+ end
+
+ def create
+ @repository.add_branch(params[:branch_name], params[:ref])
+
+ if new_branch = @repository.find_branch(params[:branch_name])
+ Event.create_ref_event(@project, current_user, new_branch, 'add')
+ end
+
+ redirect_to project_branches_path(@project)
+ end
+
+ def destroy
+ branch = @repository.find_branch(params[:id])
+
+ if branch && @repository.rm_branch(branch.name)
+ Event.create_ref_event(@project, current_user, branch, 'rm')
+ end
+
+ respond_to do |format|
+ format.html { redirect_to project_branches_path(@project) }
+ format.js { render nothing: true }
+ end
+ end
+end
diff --git a/app/controllers/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 1329410891d..bdc501d73bb 100644
--- a/app/controllers/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -1,7 +1,7 @@
# Controller for a specific Commit
#
# Not to be confused with CommitsController, plural.
-class CommitController < ProjectResourceController
+class Projects::CommitController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
@@ -11,9 +11,14 @@ class CommitController < ProjectResourceController
result = CommitLoadContext.new(project, current_user, params).execute
@commit = result[:commit]
- git_not_found! unless @commit
+
+ if @commit.nil?
+ git_not_found!
+ return
+ end
@suppress_diff = result[:suppress_diff]
+ @force_suppress_diff = result[:force_suppress_diff]
@note = result[:note]
@line_notes = result[:line_notes]
diff --git a/app/controllers/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 534ae1edd31..bdffc940ea5 100644
--- a/app/controllers/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -1,6 +1,6 @@
require "base64"
-class CommitsController < ProjectResourceController
+class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
# Authorize
@@ -13,7 +13,6 @@ class CommitsController < ProjectResourceController
@limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
@commits = @repo.commits(@ref, @path, @limit, @offset)
- @commits = CommitDecorator.decorate(@commits)
respond_to do |format|
format.html # index.html.erb
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
new file mode 100644
index 00000000000..b7531e2cefb
--- /dev/null
+++ b/app/controllers/projects/compare_controller.rb
@@ -0,0 +1,27 @@
+class Projects::CompareController < Projects::ApplicationController
+ # Authorize
+ before_filter :authorize_read_project!
+ before_filter :authorize_code_access!
+ before_filter :require_non_empty_project
+
+ def index
+ end
+
+ def show
+ compare = Gitlab::Git::Compare.new(@repository.raw_repository, params[:from], params[:to])
+
+ @commits = compare.commits
+ @commit = compare.commit
+ @diffs = compare.diffs
+ @refs_are_same = compare.same
+ @line_notes = []
+
+ diff_line_count = Commit::diff_line_count(@diffs)
+ @suppress_diff = Commit::diff_suppress?(@diffs, diff_line_count) && !params[:force_show_diff]
+ @force_suppress_diff = Commit::diff_force_suppress?(@diffs, diff_line_count)
+ end
+
+ def create
+ redirect_to project_compare_path(@project, params[:from], params[:to])
+ end
+end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
new file mode 100644
index 00000000000..0750e0a146f
--- /dev/null
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -0,0 +1,61 @@
+class Projects::DeployKeysController < Projects::ApplicationController
+ respond_to :html
+
+ # Authorize
+ before_filter :authorize_admin_project!
+
+ layout "project_settings"
+
+ def index
+ @enabled_keys = @project.deploy_keys.all
+ @available_keys = available_keys - @enabled_keys
+ end
+
+ def show
+ @key = @project.deploy_keys.find(params[:id])
+ end
+
+ def new
+ @key = @project.deploy_keys.new
+
+ respond_with(@key)
+ end
+
+ def create
+ @key = DeployKey.new(params[:deploy_key])
+
+ if @key.valid? && @project.deploy_keys << @key
+ redirect_to project_deploy_keys_path(@project)
+ else
+ render "new"
+ end
+ end
+
+ def destroy
+ @key = @project.deploy_keys.find(params[:id])
+ @key.destroy
+
+ respond_to do |format|
+ format.html { redirect_to project_deploy_keys_url }
+ format.js { render nothing: true }
+ end
+ end
+
+ def enable
+ project.deploy_keys << available_keys.find(params[:id])
+
+ redirect_to project_deploy_keys_path(@project)
+ end
+
+ def disable
+ @project.deploy_keys_projects.where(deploy_key_id: params[:id]).last.destroy
+
+ redirect_to project_deploy_keys_path(@project)
+ end
+
+ protected
+
+ def available_keys
+ @available_keys ||= current_user.accessible_deploy_keys
+ end
+end
diff --git a/app/controllers/tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb
index 2151bd7cbbd..3b945fc7126 100644
--- a/app/controllers/tree_controller.rb
+++ b/app/controllers/projects/edit_tree_controller.rb
@@ -1,5 +1,5 @@
-# Controller for viewing a repository's file structure
-class TreeController < ProjectResourceController
+# Controller for edit a repository's file
+class Projects::EditTreeController < Projects::ApplicationController
include ExtractsPath
# Authorize
@@ -7,22 +7,10 @@ class TreeController < ProjectResourceController
before_filter :authorize_code_access!
before_filter :require_non_empty_project
- before_filter :assign_ref_vars
- before_filter :edit_requirements, only: [:edit, :update]
+ before_filter :edit_requirements, only: [:show, :update]
def show
- @hex_path = Digest::SHA1.hexdigest(@path)
- @logs_path = logs_file_project_ref_path(@project, @ref, @path)
-
- respond_to do |format|
- format.html
- # Disable cache so browser history works
- format.js { no_cache_headers }
- end
- end
-
- def edit
- @last_commit = @project.repository.last_commit_for(@ref, @path).sha
+ @last_commit = Gitlab::Git::Commit.last_for_path(@project.repository, @ref, @path).sha
end
def update
@@ -34,18 +22,20 @@ class TreeController < ProjectResourceController
)
if updated_successfully
- redirect_to project_tree_path(@project, @id), notice: "Your changes have been successfully commited"
+ redirect_to project_blob_path(@project, @id), notice: "Your changes have been successfully commited"
else
flash[:notice] = "Your changes could not be commited, because the file has been changed"
- render :edit
+ render :show
end
end
private
def edit_requirements
- unless @tree.is_blob? && @tree.text?
- redirect_to project_tree_path(@project, @id), notice: "You can only edit text files"
+ @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path)
+
+ unless @blob.exists? && @blob.text?
+ redirect_to project_blob_path(@project, @id), notice: "You can only edit text files"
end
allowed = if project.protected_branch? @ref
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
new file mode 100644
index 00000000000..252d47d939e
--- /dev/null
+++ b/app/controllers/projects/graphs_controller.rb
@@ -0,0 +1,25 @@
+class Projects::GraphsController < Projects::ApplicationController
+ # Authorize
+ before_filter :authorize_read_project!
+ before_filter :authorize_code_access!
+ before_filter :require_non_empty_project
+
+ def show
+ respond_to do |format|
+ format.html
+ format.js do
+ fetch_graph
+ end
+ end
+ end
+
+ private
+
+ def fetch_graph
+ @log = @project.repository.graph_log.to_json
+ @success = true
+ rescue => ex
+ @log = []
+ @success = false
+ end
+end
diff --git a/app/controllers/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index b7d25e36252..1a94dbab5ea 100644
--- a/app/controllers/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -1,10 +1,11 @@
-class HooksController < ProjectResourceController
+class Projects::HooksController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_admin_project!, only: [:new, :create, :destroy]
+ before_filter :authorize_admin_project!
respond_to :html
+ layout "project_settings"
+
def index
@hooks = @project.hooks.all
@hook = ProjectHook.new
diff --git a/app/controllers/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 9917d198cbf..e8f845b2d17 100644
--- a/app/controllers/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -1,4 +1,4 @@
-class IssuesController < ProjectResourceController
+class Projects::IssuesController < Projects::ApplicationController
before_filter :module_enabled
before_filter :issue, only: [:edit, :update, :show]
@@ -14,9 +14,16 @@ class IssuesController < ProjectResourceController
respond_to :js, :html
def index
+ terms = params['issue_search']
+
@issues = issues_filtered
+ @issues = @issues.where("title LIKE ?", "%#{terms}%") if terms.present?
@issues = @issues.page(params[:page]).per(20)
+ 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?
+
respond_to do |format|
format.html # index.html.erb
format.js
@@ -76,37 +83,19 @@ class IssuesController < ProjectResourceController
end
end
- def sort
- return render_404 unless can?(current_user, :admin_issue, @project)
-
- @issues = @project.issues.where(id: params['issue'])
- @issues.each do |issue|
- issue.position = params['issue'].index(issue.id.to_s) + 1
- issue.save
- end
-
- render nothing: true
- end
-
- def search
- terms = params['terms']
-
- @issues = issues_filtered
- @issues = @issues.where("title LIKE ?", "%#{terms}%") unless terms.blank?
- @issues = @issues.page(params[:page]).per(100)
-
- render partial: 'issues'
- end
-
def bulk_update
- result = IssuesBulkUpdateContext.new(project, current_user, params).execute
+ result = Issues::BulkUpdateContext.new(project, current_user, params).execute
redirect_to :back, notice: "#{result[:count]} issues updated"
end
protected
def issue
- @issue ||= @project.issues.find(params[:id])
+ @issue ||= begin
+ @project.issues.find_by_iid!(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ redirect_old
+ end
end
def authorize_modify_issue!
@@ -122,6 +111,22 @@ class IssuesController < ProjectResourceController
end
def issues_filtered
- @issues = IssuesListContext.new(project, current_user, params).execute
+ @issues = Issues::ListContext.new(project, current_user, params).execute
+ end
+
+ # Since iids are implemented only in 6.1
+ # user may navigate to issue page using old global ids.
+ #
+ # To prevent 404 errors we provide a redirect to correct iids until 7.0 release
+ #
+ def redirect_old
+ issue = @project.issues.find_by_id(params[:id])
+
+ if issue
+ redirect_to project_issue_path(@project, issue)
+ return
+ else
+ raise ActiveRecord::RecordNotFound.new
+ end
end
end
diff --git a/app/controllers/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 999351e22df..65f9e2b9d57 100644
--- a/app/controllers/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -1,4 +1,4 @@
-class LabelsController < ProjectResourceController
+class Projects::LabelsController < Projects::ApplicationController
before_filter :module_enabled
# Allow read any issue
@@ -7,7 +7,13 @@ class LabelsController < ProjectResourceController
respond_to :js, :html
def index
- @labels = @project.issues_labels.order('count DESC')
+ @labels = @project.issues_labels
+ end
+
+ def generate
+ Gitlab::IssuesLabels.generate(@project)
+
+ redirect_to project_labels_path(@project)
end
protected
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
new file mode 100644
index 00000000000..0cc09caf1d2
--- /dev/null
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -0,0 +1,200 @@
+require 'gitlab/satellite/satellite'
+
+class Projects::MergeRequestsController < Projects::ApplicationController
+ before_filter :module_enabled
+ before_filter :merge_request, only: [:edit, :update, :show, :commits, :diffs, :automerge, :automerge_check, :ci_status]
+ before_filter :closes_issues, only: [:edit, :update, :show, :commits, :diffs]
+ before_filter :validates_merge_request, only: [:show, :diffs]
+ before_filter :define_show_vars, only: [:show, :diffs]
+
+ # Allow read any merge_request
+ before_filter :authorize_read_merge_request!
+
+ # Allow write(create) merge_request
+ before_filter :authorize_write_merge_request!, only: [:new, :create]
+
+ # Allow modify merge_request
+ before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
+
+ def index
+ @merge_requests = MergeRequestsLoadContext.new(project, current_user, params).execute
+ 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?
+ end
+
+ def show
+ respond_to do |format|
+ format.html
+ format.js
+
+ format.diff { render text: @merge_request.to_diff(current_user) }
+ format.patch { render text: @merge_request.to_patch(current_user) }
+ end
+ end
+
+ def diffs
+ @commit = @merge_request.last_commit
+
+ @comments_allowed = @reply_allowed = true
+ @comments_target = {noteable_type: 'MergeRequest',
+ noteable_id: @merge_request.id}
+ @line_notes = @merge_request.notes.where("line_code is not null")
+
+ diff_line_count = Commit::diff_line_count(@merge_request.diffs)
+ @suppress_diff = Commit::diff_suppress?(@merge_request.diffs, diff_line_count) && !params[:force_show_diff]
+ @force_suppress_diff = Commit::diff_force_suppress?(@merge_request.diffs, diff_line_count)
+ end
+
+ 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
+ @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names
+ @source_project = @merge_request.source_project
+ @merge_request
+ end
+
+ def edit
+ @source_project = @merge_request.source_project
+ @target_project = @merge_request.target_project
+ @target_branches = @merge_request.target_project.repository.branch_names
+ end
+
+ def create
+ @merge_request = MergeRequest.new(params[:merge_request])
+ @merge_request.author = current_user
+ @target_branches ||= []
+ if @merge_request.save
+ @merge_request.reload_code
+ redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.'
+ else
+ @source_project = @merge_request.source_project
+ @target_project = @merge_request.target_project
+ render action: "new"
+ end
+ end
+
+ def update
+ 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
+ redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.'
+ else
+ render "edit"
+ end
+ end
+
+ def automerge_check
+ if @merge_request.unchecked?
+ @merge_request.check_if_can_be_merged
+ end
+ render json: {merge_status: @merge_request.merge_status_name}
+ rescue Gitlab::SatelliteNotExistError
+ render json: {merge_status: :no_satellite}
+ end
+
+ def automerge
+ return access_denied! unless allowed_to_merge?
+
+ if @merge_request.opened? && @merge_request.can_be_merged?
+ @merge_request.should_remove_source_branch = params[:should_remove_source_branch]
+ @merge_request.automerge!(current_user)
+ @status = true
+ else
+ @status = false
+ end
+ end
+
+ def branch_from
+ #This is always source
+ @source_project = @merge_request.nil? ? @project : @merge_request.source_project
+ @commit = @repository.commit(params[:ref]) if params[:ref].present?
+ end
+
+ def branch_to
+ @target_project = selected_target_project
+ @commit = @target_project.repository.commit(params[:ref]) if params[:ref].present?
+ end
+
+ def update_branches
+ @target_project = selected_target_project
+ @target_branches = @target_project.repository.branch_names
+ @target_branches
+ end
+
+ def ci_status
+ status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
+ response = {status: status}
+
+ render json: response
+ end
+
+ protected
+
+ def selected_target_project
+ ((@project.id.to_s == params[:target_project_id]) || @project.forked_project_link.nil?) ? @project : @project.forked_project_link.forked_from_project
+ end
+
+ def merge_request
+ @merge_request ||= @project.merge_requests.find_by_iid!(params[:id])
+ end
+
+ def closes_issues
+ @closes_issues ||= @merge_request.closes_issues
+ end
+
+ def authorize_modify_merge_request!
+ return render_404 unless can?(current_user, :modify_merge_request, @merge_request)
+ end
+
+ def authorize_admin_merge_request!
+ return render_404 unless can?(current_user, :admin_merge_request, @merge_request)
+ end
+
+ def module_enabled
+ return render_404 unless @project.merge_requests_enabled
+ end
+
+ def validates_merge_request
+ # Show git not found page
+ # if there is no saved commits between source & target branch
+ if @merge_request.commits.blank?
+ # and if source target doesn't exist
+ return invalid_mr unless @merge_request.target_project.repository.branch_names.include?(@merge_request.target_branch)
+
+ # or if source branch doesn't exist
+ return invalid_mr unless @merge_request.source_project.repository.branch_names.include?(@merge_request.source_branch)
+ end
+ end
+
+ def define_show_vars
+ # Build a note object for comment form
+ @note = @project.notes.new(noteable: @merge_request)
+
+ # Get commits from repository
+ # or from cache if already merged
+ @commits = @merge_request.commits
+
+ @allowed_to_merge = allowed_to_merge?
+ @show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge
+
+ @target_type = :merge_request
+ @target_id = @merge_request.id
+ end
+
+ def allowed_to_merge?
+ action = if project.protected_branch?(@merge_request.target_branch)
+ :push_code_to_protected_branches
+ else
+ :push_code
+ end
+
+ can?(current_user, action, @project)
+ end
+
+ def invalid_mr
+ # Render special view for MR with removed source or target branch
+ render 'invalid'
+ end
+end
diff --git a/app/controllers/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index a0c824e8abb..39cd579cce5 100644
--- a/app/controllers/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -1,4 +1,4 @@
-class MilestonesController < ProjectResourceController
+class Projects::MilestonesController < Projects::ApplicationController
before_filter :module_enabled
before_filter :milestone, only: [:edit, :update, :destroy, :show]
@@ -12,9 +12,9 @@ class MilestonesController < ProjectResourceController
def index
@milestones = case params[:f]
- when 'all'; @project.milestones.order("closed, due_date DESC")
+ when 'all'; @project.milestones.order("state, due_date DESC")
when 'closed'; @project.milestones.closed.order("due_date DESC")
- else @project.milestones.active.order("due_date ASC")
+ else @project.milestones.active.order("due_date DESC")
end
@milestones = @milestones.includes(:project)
@@ -32,7 +32,7 @@ class MilestonesController < ProjectResourceController
def show
@issues = @milestone.issues
- @users = UserDecorator.decorate(@milestone.participants)
+ @users = @milestone.participants.uniq
@merge_requests = @milestone.merge_requests
respond_to do |format|
@@ -81,7 +81,7 @@ class MilestonesController < ProjectResourceController
protected
def milestone
- @milestone ||= @project.milestones.find(params[:id])
+ @milestone ||= @project.milestones.find_by_iid!(params[:id])
end
def authorize_admin_milestone!
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
new file mode 100644
index 00000000000..9832495c64f
--- /dev/null
+++ b/app/controllers/projects/network_controller.rb
@@ -0,0 +1,19 @@
+class Projects::NetworkController < Projects::ApplicationController
+ include ExtractsPath
+ include ApplicationHelper
+
+ # Authorize
+ before_filter :authorize_read_project!
+ before_filter :authorize_code_access!
+ before_filter :require_non_empty_project
+
+ def show
+ respond_to do |format|
+ format.html
+
+ format.json do
+ @graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref])
+ end
+ end
+ end
+end
diff --git a/app/controllers/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 000c7bbb641..8214163c315 100644
--- a/app/controllers/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -1,4 +1,4 @@
-class NotesController < ProjectResourceController
+class Projects::NotesController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_note!
before_filter :authorize_write_note!, only: [:create]
@@ -38,6 +38,32 @@ class NotesController < ProjectResourceController
end
end
+ def update
+ @note = @project.notes.find(params[:id])
+ return access_denied! unless can?(current_user, :admin_note, @note)
+
+ @note.update_attributes(params[:note])
+
+ respond_to do |format|
+ format.js do
+ render js: { success: @note.valid?, id: @note.id, note: view_context.markdown(@note.note) }.to_json
+ end
+ format.html do
+ redirect_to :back
+ end
+ end
+ end
+
+ def delete_attachment
+ @note = @project.notes.find(params[:id])
+ @note.remove_attachment!
+ @note.update_attribute(:attachment, nil)
+
+ respond_to do |format|
+ format.js { render nothing: true }
+ end
+ end
+
def preview
render text: view_context.markdown(params[:note])
end
@@ -71,7 +97,6 @@ class NotesController < ProjectResourceController
# Helps to distinguish e.g. commit notes in mr notes list
def note_for_main_target?(note)
- note.for_wall? ||
- (@target_type.camelize == note.noteable_type && !note.for_diff_line?)
+ (@target_type.camelize == note.noteable_type && !note.for_diff_line?)
end
end
diff --git a/app/controllers/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index fd2734eff84..81531bb0ac0 100644
--- a/app/controllers/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -1,4 +1,4 @@
-class ProtectedBranchesController < ProjectResourceController
+class Projects::ProtectedBranchesController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :require_non_empty_project
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
new file mode 100644
index 00000000000..0c23d411f4c
--- /dev/null
+++ b/app/controllers/projects/raw_controller.rb
@@ -0,0 +1,33 @@
+# Controller for viewing a file's raw
+class Projects::RawController < Projects::ApplicationController
+ include ExtractsPath
+
+ # Authorize
+ before_filter :authorize_read_project!
+ before_filter :authorize_code_access!
+ before_filter :require_non_empty_project
+
+ def show
+ @blob = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, @path)
+
+ if @blob.exists?
+ type = if @blob.mime_type =~ /html|javascript/
+ 'text/plain; charset=utf-8'
+ else
+ @blob.mime_type
+ end
+
+ headers['X-Content-Type-Options'] = 'nosniff'
+
+ send_data(
+ @blob.data,
+ type: type,
+ disposition: 'inline',
+ filename: @blob.name
+ )
+ else
+ not_found!
+ end
+ end
+end
+
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
new file mode 100644
index 00000000000..e5c090e1f4d
--- /dev/null
+++ b/app/controllers/projects/refs_controller.rb
@@ -0,0 +1,43 @@
+class Projects::RefsController < Projects::ApplicationController
+ include ExtractsPath
+
+ # Authorize
+ before_filter :authorize_read_project!
+ before_filter :authorize_code_access!
+ before_filter :require_non_empty_project
+
+ def switch
+ respond_to do |format|
+ format.html do
+ new_path = if params[:destination] == "tree"
+ project_tree_path(@project, (@id))
+ elsif params[:destination] == "blob"
+ project_blob_path(@project, (@id))
+ elsif params[:destination] == "graph"
+ project_network_path(@project, @id, @options)
+ else
+ project_commits_path(@project, @id)
+ end
+
+ redirect_to new_path
+ end
+ format.js do
+ @ref = params[:ref]
+ define_tree_vars
+ render "tree"
+ end
+ end
+ end
+
+ def logs_tree
+ contents = @tree.entries
+ @logs = contents.map do |content|
+ file = params[:path] ? File.join(params[:path], content.name) : content.name
+ last_commit = @repo.commits(@commit.id, file, 1).last
+ {
+ file_name: content.name,
+ commit: last_commit
+ }
+ end
+ end
+end
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 229cb36949b..20e2a9311ee 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -1,23 +1,11 @@
-class RepositoriesController < ProjectResourceController
+class Projects::RepositoriesController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :require_non_empty_project
- def show
- @activities = @repository.commits_with_refs(20)
- end
-
- def branches
- @branches = @repository.branches
- end
-
- def tags
- @tags = @repository.tags
- end
-
def stats
- @stats = Gitlab::GitStats.new(@repository.raw, @repository.root_ref)
+ @stats = Gitlab::Git::Stats.new(@repository.raw, @repository.root_ref)
@graph = @stats.graph
end
@@ -26,8 +14,9 @@ class RepositoriesController < ProjectResourceController
render_404 and return
end
+ storage_path = Rails.root.join("tmp", "repositories")
- file_path = @repository.archive_repo(params[:ref])
+ file_path = @repository.archive_repo(params[:ref], storage_path)
if file_path
# Send file to user
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
new file mode 100644
index 00000000000..6db22186c14
--- /dev/null
+++ b/app/controllers/projects/services_controller.rb
@@ -0,0 +1,39 @@
+class Projects::ServicesController < Projects::ApplicationController
+ # Authorize
+ before_filter :authorize_admin_project!
+ before_filter :service, only: [:edit, :update, :test]
+
+ respond_to :html
+
+ layout "project_settings"
+
+ def index
+ @project.build_missing_services
+ @services = @project.services.reload
+ end
+
+ def edit
+ end
+
+ def update
+ if @service.update_attributes(params[:service])
+ redirect_to edit_project_service_path(@project, @service.to_param)
+ else
+ render 'edit'
+ end
+ end
+
+ def test
+ data = GitPushService.new.sample_data(project, current_user)
+
+ @service.execute(data)
+
+ redirect_to :back
+ end
+
+ private
+
+ def service
+ @service ||= @project.services.find { |service| service.to_param == params[:id] }
+ end
+end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
new file mode 100644
index 00000000000..dd0c1a57089
--- /dev/null
+++ b/app/controllers/projects/snippets_controller.rb
@@ -0,0 +1,89 @@
+class Projects::SnippetsController < Projects::ApplicationController
+ before_filter :module_enabled
+ before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw]
+
+ # Allow read any snippet
+ before_filter :authorize_read_project_snippet!
+
+ # Allow write(create) snippet
+ before_filter :authorize_write_project_snippet!, only: [:new, :create]
+
+ # Allow modify snippet
+ before_filter :authorize_modify_project_snippet!, only: [:edit, :update]
+
+ # Allow destroy snippet
+ before_filter :authorize_admin_project_snippet!, only: [:destroy]
+
+ respond_to :html
+
+ def index
+ @snippets = @project.snippets.fresh.non_expired
+ end
+
+ def new
+ @snippet = @project.snippets.build
+ end
+
+ def create
+ @snippet = @project.snippets.build(params[:project_snippet])
+ @snippet.author = current_user
+
+ if @snippet.save
+ redirect_to project_snippet_path(@project, @snippet)
+ else
+ respond_with(@snippet)
+ end
+ end
+
+ def edit
+ end
+
+ def update
+ if @snippet.update_attributes(params[:project_snippet])
+ redirect_to project_snippet_path(@project, @snippet)
+ else
+ respond_with(@snippet)
+ end
+ end
+
+ def show
+ @note = @project.notes.new(noteable: @snippet)
+ @target_type = :snippet
+ @target_id = @snippet.id
+ end
+
+ def destroy
+ return access_denied! unless can?(current_user, :admin_project_snippet, @snippet)
+
+ @snippet.destroy
+
+ redirect_to project_snippets_path(@project)
+ end
+
+ def raw
+ send_data(
+ @snippet.content,
+ type: "text/plain",
+ disposition: 'inline',
+ filename: @snippet.file_name
+ )
+ end
+
+ protected
+
+ def snippet
+ @snippet ||= @project.snippets.find(params[:id])
+ end
+
+ def authorize_modify_project_snippet!
+ return render_404 unless can?(current_user, :modify_project_snippet, @snippet)
+ end
+
+ def authorize_admin_project_snippet!
+ return render_404 unless can?(current_user, :admin_project_snippet, @snippet)
+ end
+
+ def module_enabled
+ return render_404 unless @project.snippets_enabled
+ end
+end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
new file mode 100644
index 00000000000..9dbb0d81888
--- /dev/null
+++ b/app/controllers/projects/tags_controller.rb
@@ -0,0 +1,36 @@
+class Projects::TagsController < Projects::ApplicationController
+ # Authorize
+ before_filter :authorize_read_project!
+ before_filter :require_non_empty_project
+
+ before_filter :authorize_code_access!
+ before_filter :authorize_push!, only: [:create]
+ before_filter :authorize_admin_project!, only: [:destroy]
+
+ def index
+ @tags = Kaminari.paginate_array(@repository.tags).page(params[:page]).per(30)
+ end
+
+ def create
+ @repository.add_tag(params[:tag_name], params[:ref])
+
+ if new_tag = @repository.find_tag(params[:tag_name])
+ Event.create_ref_event(@project, current_user, new_tag, 'add', 'refs/tags')
+ end
+
+ redirect_to project_tags_path(@project)
+ end
+
+ def destroy
+ tag = @repository.find_tag(params[:id])
+
+ if tag && @repository.rm_tag(tag.name)
+ Event.create_ref_event(@project, current_user, tag, 'rm', 'refs/tags')
+ end
+
+ respond_to do |format|
+ format.html { redirect_to project_tags_path }
+ format.js { render nothing: true }
+ end
+ end
+end
diff --git a/app/controllers/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb
index 18d4ae3ac96..b4b318fa59e 100644
--- a/app/controllers/team_members_controller.rb
+++ b/app/controllers/projects/team_members_controller.rb
@@ -1,15 +1,12 @@
-class TeamMembersController < ProjectResourceController
+class Projects::TeamMembersController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_admin_project!, except: [:index, :show]
+ before_filter :authorize_admin_project!
- def index
- @teams = UserTeam.scoped
- end
+ layout "project_settings"
- def show
- @user_project_relation = project.users_projects.find_by_user_id(member)
- @events = member.recent_events.in_projects(project).limit(7)
+ def index
+ @group = @project.group
+ @users_projects = @project.users_projects.order('project_access DESC')
end
def new
@@ -17,7 +14,7 @@ class TeamMembersController < ProjectResourceController
end
def create
- users = User.where(id: params[:user_ids])
+ users = User.where(id: params[:user_ids].split(','))
@project.team << [users, params[:project_access]]
@@ -51,9 +48,9 @@ class TeamMembersController < ProjectResourceController
def apply_import
giver = Project.find(params[:source_project_id])
status = @project.team.import(giver)
- notice = status ? "Succesfully imported" : "Import failed"
+ notice = status ? "Successfully imported" : "Import failed"
- redirect_to project_team_members_path(project), notice: notice
+ redirect_to project_team_index_path(project), notice: notice
end
protected
diff --git a/app/controllers/projects/teams_controller.rb b/app/controllers/projects/teams_controller.rb
deleted file mode 100644
index 3ca724aaf4d..00000000000
--- a/app/controllers/projects/teams_controller.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-class Projects::TeamsController < Projects::ApplicationController
-
- def available
- @teams = current_user.is_admin? ? UserTeam.scoped : current_user.user_teams
- @teams = @teams.without_project(project)
- unless @teams.any?
- redirect_to project_team_index_path(project), notice: "No avaliable teams for assigment."
- end
- end
-
- def assign
- unless params[:team_id].blank?
- team = UserTeam.find(params[:team_id])
- access = params[:greatest_project_access]
- team.assign_to_project(project, access)
- end
- redirect_to project_team_index_path(project)
- end
-
- def resign
- team = project.user_teams.find_by_path(params[:id])
- team.resign_from_project(project)
-
- redirect_to project_team_index_path(project)
- end
-
-end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
new file mode 100644
index 00000000000..5d543f35665
--- /dev/null
+++ b/app/controllers/projects/tree_controller.rb
@@ -0,0 +1,17 @@
+# Controller for viewing a repository's file structure
+class Projects::TreeController < Projects::ApplicationController
+ include ExtractsPath
+
+ # Authorize
+ before_filter :authorize_read_project!
+ before_filter :authorize_code_access!
+ before_filter :require_non_empty_project
+
+ def show
+ respond_to do |format|
+ format.html
+ # Disable cache so browser history works
+ format.js { no_cache_headers }
+ end
+ end
+end
diff --git a/app/controllers/projects/walls_controller.rb b/app/controllers/projects/walls_controller.rb
new file mode 100644
index 00000000000..834215a1473
--- /dev/null
+++ b/app/controllers/projects/walls_controller.rb
@@ -0,0 +1,20 @@
+class Projects::WallsController < Projects::ApplicationController
+ before_filter :module_enabled
+
+ respond_to :js, :html
+
+ def show
+ @note = @project.notes.new
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ protected
+
+ def module_enabled
+ return render_404 unless @project.wall_enabled
+ end
+end
+
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
new file mode 100644
index 00000000000..797f3d3dd41
--- /dev/null
+++ b/app/controllers/projects/wikis_controller.rb
@@ -0,0 +1,95 @@
+class Projects::WikisController < Projects::ApplicationController
+ before_filter :authorize_read_wiki!
+ before_filter :authorize_write_wiki!, only: [:edit, :create, :history]
+ before_filter :authorize_admin_wiki!, only: :destroy
+ before_filter :load_gollum_wiki
+
+ def pages
+ @wiki_pages = @gollum_wiki.pages
+ end
+
+ def show
+ @wiki = @gollum_wiki.find_page(params[:id], params[:version_id])
+
+ if @wiki
+ render 'show'
+ else
+ return render('empty') unless can?(current_user, :write_wiki, @project)
+ @wiki = WikiPage.new(@gollum_wiki)
+ @wiki.title = params[:id]
+
+ render 'edit'
+ end
+ end
+
+ def edit
+ @wiki = @gollum_wiki.find_page(params[:id])
+ end
+
+ def update
+ @wiki = @gollum_wiki.find_page(params[:id])
+
+ return render('empty') unless can?(current_user, :write_wiki, @project)
+
+ if @wiki.update(content, format, message)
+ redirect_to [@project, @wiki], notice: 'Wiki was successfully updated.'
+ else
+ render 'edit'
+ end
+ end
+
+ def create
+ @wiki = WikiPage.new(@gollum_wiki)
+
+ if @wiki.create(wiki_params)
+ redirect_to project_wiki_path(@project, @wiki), notice: 'Wiki was successfully updated.'
+ else
+ render action: "edit"
+ end
+ end
+
+ def history
+ @wiki = @gollum_wiki.find_page(params[:id])
+
+ redirect_to(project_wiki_path(@project, :home), notice: "Page not found") unless @wiki
+ end
+
+ def destroy
+ @wiki = @gollum_wiki.find_page(params[:id])
+ @wiki.delete if @wiki
+ redirect_to project_wiki_path(@project, :home), notice: "Page was successfully deleted"
+ end
+
+ def git_access
+ end
+
+ private
+
+ def load_gollum_wiki
+ @gollum_wiki = GollumWiki.new(@project, current_user)
+
+ # Call #wiki to make sure the Wiki Repo is initialized
+ @gollum_wiki.wiki
+ rescue GollumWiki::CouldNotCreateWikiError => ex
+ flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
+ redirect_to @project
+ return false
+ end
+
+ def wiki_params
+ params[:wiki].slice(:title, :content, :format, :message)
+ end
+
+ def content
+ params[:wiki][:content]
+ end
+
+ def format
+ params[:wiki][:format]
+ end
+
+ def message
+ params[:wiki][:message]
+ end
+
+end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 5da3fbf591c..7264128691e 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,21 +1,22 @@
-require Rails.root.join('lib', 'gitlab', 'graph', 'json_builder')
-
-class ProjectsController < ProjectResourceController
- skip_before_filter :project, only: [:new, :create]
- skip_before_filter :repository, only: [:new, :create]
+class ProjectsController < ApplicationController
+ skip_before_filter :authenticate_user!, only: [:show]
+ before_filter :project, except: [:new, :create]
+ before_filter :repository, except: [:new, :create]
# Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create]
- before_filter :authorize_admin_project!, only: [:edit, :update, :destroy]
+ before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer]
before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
- layout 'application', only: [:new, :create]
+ layout 'navless', only: [:new, :create, :fork]
+ before_filter :set_title, only: [:new, :create]
def new
@project = Project.new
end
def edit
+ render 'edit', layout: "project_settings"
end
def create
@@ -27,7 +28,7 @@ class ProjectsController < ProjectResourceController
if @project.saved?
redirect_to @project
else
- render action: "new"
+ render "new"
end
end
format.js
@@ -35,69 +36,97 @@ class ProjectsController < ProjectResourceController
end
def update
- status = ::Projects::UpdateContext.new(project, current_user, params).execute
+ status = ::Projects::UpdateContext.new(@project, current_user, params).execute
respond_to do |format|
if status
flash[:notice] = 'Project was successfully updated.'
- format.html { redirect_to edit_project_path(project), notice: 'Project was successfully updated.' }
+ format.html { redirect_to edit_project_path(@project), notice: 'Project was successfully updated.' }
format.js
else
- format.html { render action: "edit" }
+ format.html { render "edit", layout: "project_settings" }
format.js
end
end
+ end
- rescue Project::TransferError => ex
- @error = ex
- render :update_failed
+ def transfer
+ ::Projects::TransferContext.new(project, current_user, params).execute
end
def show
+ return authenticate_user! unless @project.public || current_user
+
limit = (params[:limit] || 20).to_i
- @events = @project.events.recent.limit(limit).offset(params[:offset] || 0)
+ @events = @project.events.recent
+ @events = event_filter.apply_filter(@events)
+ @events = @events.limit(limit).offset(params[:offset] || 0)
+
+ # Ensure project default branch is set if it possible
+ # Normally it defined on push or during creation
+ @project.discover_default_branch
respond_to do |format|
format.html do
- if @project.repository && !@project.repository.empty?
- @last_push = current_user.recent_push(@project.id)
- render :show
+ if @project.empty_repo?
+ render "projects/empty", layout: user_layout
else
- render "projects/empty"
+ if current_user
+ @last_push = current_user.recent_push(@project.id)
+ end
+ render :show, layout: user_layout
end
end
format.js
end
end
- def files
- @notes = @project.notes.where("attachment != 'NULL'").order("created_at DESC").limit(100)
- end
+ def destroy
+ return access_denied! unless can?(current_user, :remove_project, project)
- #
- # Wall
- #
+ project.team.truncate
+ project.destroy
- def wall
- return render_404 unless @project.wall_enabled
+ respond_to do |format|
+ format.html { redirect_to root_path }
+ end
+ end
- @target_type = :wall
- @target_id = nil
- @note = @project.notes.new
+ def fork
+ @forked_project = ::Projects::ForkContext.new(project, current_user).execute
respond_to do |format|
- format.html
+ format.html do
+ if @forked_project.saved? && @forked_project.forked?
+ redirect_to(@forked_project, notice: 'Project was successfully forked.')
+ else
+ @title = 'Fork project'
+ render "fork"
+ end
+ end
+ format.js
end
end
- def destroy
- return access_denied! unless can?(current_user, :remove_project, project)
-
- project.team.truncate
- project.destroy
+ def autocomplete_sources
+ @suggestions = {
+ emojis: Emoji.names,
+ issues: @project.issues.select([:iid, :title, :description]),
+ members: @project.team.members.sort_by(&:username).map { |user| { username: user.username, name: user.name } }
+ }
respond_to do |format|
- format.html { redirect_to root_path }
+ format.json { render :json => @suggestions }
end
end
+
+ private
+
+ def set_title
+ @title = 'New Project'
+ end
+
+ def user_layout
+ current_user ? "projects" : "public_projects"
+ end
end
diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb
index b929b23e68c..87e903a1d2d 100644
--- a/app/controllers/public/projects_controller.rb
+++ b/app/controllers/public/projects_controller.rb
@@ -1,12 +1,13 @@
class Public::ProjectsController < ApplicationController
skip_before_filter :authenticate_user!,
- :reject_blocked, :set_current_user_for_observers,
- :add_abilities
+ :reject_blocked, :set_current_user_for_observers,
+ :add_abilities
layout 'public'
def index
@projects = Project.public_only
+ @projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
end
end
diff --git a/app/controllers/refs_controller.rb b/app/controllers/refs_controller.rb
deleted file mode 100644
index 0e4dba3dc4b..00000000000
--- a/app/controllers/refs_controller.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-class RefsController < ProjectResourceController
-
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
- before_filter :require_non_empty_project
-
- before_filter :ref
- before_filter :define_tree_vars, only: [:blob, :logs_tree]
-
- def switch
- respond_to do |format|
- format.html do
- new_path = if params[:destination] == "tree"
- project_tree_path(@project, (@ref + "/" + params[:path]))
- elsif params[:destination] == "graph"
- project_graph_path(@project, @ref)
- else
- project_commits_path(@project, @ref)
- end
-
- redirect_to new_path
- end
- format.js do
- @ref = params[:ref]
- define_tree_vars
- render "tree"
- end
- end
- end
-
- def logs_tree
- contents = @tree.contents
- @logs = contents.map do |content|
- file = params[:path] ? File.join(params[:path], content.name) : content.name
- last_commit = @repo.commits(@commit.id, file, 1).last
- last_commit = CommitDecorator.decorate(last_commit)
- {
- file_name: content.name,
- commit: last_commit
- }
- end
- end
-
- protected
-
- def define_tree_vars
- params[:path] = nil if params[:path].blank?
-
- @repo = project.repository
- @commit = @repo.commit(@ref)
- @commit = CommitDecorator.decorate(@commit)
- @tree = Tree.new(@commit.tree, @ref, params[:path])
- @tree = TreeDecorator.new(@tree)
- @hex_path = Digest::SHA1.hexdigest(params[:path] || "")
-
- if params[:path]
- @logs_path = logs_file_project_ref_path(@project, @ref, params[:path])
- else
- @logs_path = logs_tree_project_ref_path(@project, @ref)
- end
- rescue
- return render_404
- end
-
- def ref
- @ref = params[:id] || params[:ref]
- end
-end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index b7ee75619cb..5f18bac82ed 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -2,9 +2,6 @@ class RegistrationsController < Devise::RegistrationsController
before_filter :signup_enabled?
def destroy
- if current_user.owned_projects.count > 0
- redirect_to account_profile_path, alert: "Remove projects and groups before removing account." and return
- end
current_user.destroy
respond_to do |format|
@@ -12,9 +9,16 @@ class RegistrationsController < Devise::RegistrationsController
end
end
+ protected
+
+ def build_resource(hash=nil)
+ super
+ self.resource.with_defaults
+ end
+
private
def signup_enabled?
redirect_to new_user_session_path unless Gitlab.config.gitlab.signup_enabled
end
-end \ No newline at end of file
+end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index bbd67df6c70..f5c3bb133ed 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -6,9 +6,11 @@ class SearchController < ApplicationController
project_ids = current_user.authorized_projects.map(&:id)
if group_id.present?
- group_project_ids = Group.find(group_id).projects.map(&:id)
+ @group = Group.find(group_id)
+ group_project_ids = @group.projects.map(&:id)
project_ids.select! { |id| group_project_ids.include?(id)}
elsif project_id.present?
+ @project = Project.find(params[:project_id])
project_ids.select! { |id| id == project_id.to_i}
end
@@ -18,5 +20,7 @@ class SearchController < ApplicationController
@merge_requests = result[:merge_requests]
@issues = result[:issues]
@wiki_pages = result[:wiki_pages]
+ @blobs = Kaminari.paginate_array(result[:blobs]).page(params[:page]).per(20)
+ @total_results = @projects.count + @merge_requests.count + @issues.count + @wiki_pages.count + @blobs.total_count
end
end
diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb
deleted file mode 100644
index d0df469b967..00000000000
--- a/app/controllers/services_controller.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-class ServicesController < ProjectResourceController
- # Authorize
- before_filter :authorize_admin_project!
-
- respond_to :html
-
- def index
- @gitlab_ci_service = @project.gitlab_ci_service
- end
-
- def edit
- @service = @project.gitlab_ci_service
-
- # Create if missing
- @service = @project.create_gitlab_ci_service unless @service
- end
-
- def update
- @service = @project.gitlab_ci_service
-
- if @service.update_attributes(params[:service])
- redirect_to edit_project_service_path(@project, :gitlab_ci)
- else
- render 'edit'
- end
- end
-
- def test
- commits = project.repository.commits(project.default_branch, nil, 3)
- data = project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", current_user)
-
- @service = project.gitlab_ci_service
- @service.execute(data)
-
- redirect_to :back
- end
-end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 26898abfa82..b91f68aab5e 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,37 +1,60 @@
-class SnippetsController < ProjectResourceController
+class SnippetsController < ApplicationController
before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw]
- # Allow read any snippet
- before_filter :authorize_read_snippet!
-
- # Allow write(create) snippet
- before_filter :authorize_write_snippet!, only: [:new, :create]
-
# Allow modify snippet
before_filter :authorize_modify_snippet!, only: [:edit, :update]
# Allow destroy snippet
before_filter :authorize_admin_snippet!, only: [:destroy]
+ before_filter :set_title
+
respond_to :html
+ layout 'navless'
+
def index
- @snippets = @project.snippets.fresh.non_expired
+ @snippets = Snippet.public.fresh.non_expired.page(params[:page]).per(20)
+ end
+
+ def user_index
+ @user = User.find_by_username(params[:username])
+ @snippets = @user.snippets.fresh.non_expired
+
+ if @user == current_user
+ @snippets = case params[:scope]
+ when 'public' then
+ @snippets.public
+ when 'private' then
+ @snippets.private
+ else
+ @snippets
+ end
+ else
+ @snippets = @snippets.public
+ end
+
+ @snippets = @snippets.page(params[:page]).per(20)
+
+ if @user == current_user
+ render 'current_user_index'
+ else
+ render 'user_index'
+ end
end
def new
- @snippet = @project.snippets.new
+ @snippet = PersonalSnippet.new
end
def create
- @snippet = @project.snippets.new(params[:snippet])
+ @snippet = PersonalSnippet.new(params[:personal_snippet])
@snippet.author = current_user
- @snippet.save
- if @snippet.valid?
- redirect_to [@project, @snippet]
+ if @snippet.save
+ redirect_to snippet_path(@snippet)
else
- respond_with(@snippet)
+ respond_with @snippet
end
end
@@ -39,27 +62,22 @@ class SnippetsController < ProjectResourceController
end
def update
- @snippet.update_attributes(params[:snippet])
-
- if @snippet.valid?
- redirect_to [@project, @snippet]
+ if @snippet.update_attributes(params[:personal_snippet])
+ redirect_to snippet_path(@snippet)
else
- respond_with(@snippet)
+ respond_with @snippet
end
end
def show
- @note = @project.notes.new(noteable: @snippet)
- @target_type = :snippet
- @target_id = @snippet.id
end
def destroy
- return access_denied! unless can?(current_user, :admin_snippet, @snippet)
+ return access_denied! unless can?(current_user, :admin_personal_snippet, @snippet)
@snippet.destroy
- redirect_to project_snippets_path(@project)
+ redirect_to snippets_path
end
def raw
@@ -74,14 +92,18 @@ class SnippetsController < ProjectResourceController
protected
def snippet
- @snippet ||= @project.snippets.find(params[:id])
+ @snippet ||= PersonalSnippet.where('author_id = :user_id or private is false', user_id: current_user.id).find(params[:id])
end
def authorize_modify_snippet!
- return render_404 unless can?(current_user, :modify_snippet, @snippet)
+ return render_404 unless can?(current_user, :modify_personal_snippet, @snippet)
end
def authorize_admin_snippet!
- return render_404 unless can?(current_user, :admin_snippet, @snippet)
+ return render_404 unless can?(current_user, :admin_personal_snippet, @snippet)
+ end
+
+ def set_title
+ @title = 'Snippets'
end
end
diff --git a/app/controllers/teams/application_controller.rb b/app/controllers/teams/application_controller.rb
deleted file mode 100644
index fc23202610c..00000000000
--- a/app/controllers/teams/application_controller.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-class Teams::ApplicationController < ApplicationController
-
- layout 'user_team'
-
- before_filter :authorize_manage_user_team!
-
- protected
-
- def user_team
- @team ||= UserTeam.find_by_path(params[:team_id])
- end
-
-end
diff --git a/app/controllers/teams/members_controller.rb b/app/controllers/teams/members_controller.rb
deleted file mode 100644
index db218b8ca5e..00000000000
--- a/app/controllers/teams/members_controller.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-class Teams::MembersController < Teams::ApplicationController
-
- skip_before_filter :authorize_manage_user_team!, only: [:index]
-
- def index
- @members = user_team.members
- end
-
- def new
- @users = User.potential_team_members(user_team)
- @users = UserDecorator.decorate @users
- end
-
- def create
- unless params[:user_ids].blank?
- user_ids = params[:user_ids]
- access = params[:default_project_access]
- is_admin = params[:group_admin]
- user_team.add_members(user_ids, access, is_admin)
- end
-
- redirect_to team_members_path(user_team), notice: 'Members was successfully added into Team of users.'
- end
-
- def edit
- team_member
- end
-
- def update
- options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]}
- if user_team.update_membership(team_member, options)
- redirect_to team_members_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
- else
- render :edit
- end
- end
-
- def destroy
- user_team.remove_member(team_member)
- redirect_to team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users."
- end
-
- protected
-
- def team_member
- @member ||= user_team.members.find_by_username(params[:id])
- end
-
-end
diff --git a/app/controllers/teams/projects_controller.rb b/app/controllers/teams/projects_controller.rb
deleted file mode 100644
index e87889b4046..00000000000
--- a/app/controllers/teams/projects_controller.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-class Teams::ProjectsController < Teams::ApplicationController
-
- skip_before_filter :authorize_manage_user_team!, only: [:index]
-
- def index
- @projects = user_team.projects
- @avaliable_projects = current_user.admin? ? Project.without_team(user_team) : current_user.owned_projects.without_team(user_team)
- end
-
- def new
- user_team
- @avaliable_projects = current_user.owned_projects.scoped
- @avaliable_projects = @avaliable_projects.without_team(user_team) if user_team.projects.any?
-
- redirect_to team_projects_path(user_team), notice: "No avalible projects." unless @avaliable_projects.any?
- end
-
- def create
- redirect_to :back if params[:project_ids].blank?
-
- project_ids = params[:project_ids]
- access = params[:greatest_project_access]
-
- # Reject non-allowed projects
- allowed_project_ids = current_user.owned_projects.map(&:id)
- project_ids.select! { |id| allowed_project_ids.include?(id.to_i) }
-
- # Assign projects to team
- user_team.assign_to_projects(project_ids, access)
-
- redirect_to team_projects_path(user_team), notice: 'Team of users was successfully assigned to projects.'
- end
-
- def edit
- team_project
- end
-
- def update
- if user_team.update_project_access(team_project, params[:greatest_project_access])
- redirect_to team_projects_path(user_team), notice: 'Access was successfully updated.'
- else
- render :edit
- end
- end
-
- def destroy
- user_team.resign_from_project(team_project)
- redirect_to team_projects_path(user_team), notice: 'Team of users was successfully reassigned from project.'
- end
-
- private
-
- def team_project
- @project ||= user_team.projects.find_with_namespace(params[:id])
- end
-
-end
diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb
deleted file mode 100644
index ef66b77e232..00000000000
--- a/app/controllers/teams_controller.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-class TeamsController < ApplicationController
- # Authorize
- before_filter :authorize_create_team!, only: [:new, :create]
- before_filter :authorize_manage_user_team!, only: [:edit, :update]
- before_filter :authorize_admin_user_team!, only: [:destroy]
-
- before_filter :user_team, except: [:new, :create]
-
- layout 'user_team', except: [:new, :create]
-
- def show
- user_team
- projects
- @events = Event.in_projects(user_team.project_ids).limit(20).offset(params[:offset] || 0)
- end
-
- def edit
- user_team
- end
-
- def update
- if user_team.update_attributes(params[:user_team])
- redirect_to team_path(user_team)
- else
- render action: :edit
- end
- end
-
- def destroy
- user_team.destroy
- redirect_to dashboard_path
- end
-
- def new
- @team = UserTeam.new
- end
-
- def create
- @team = UserTeam.new(params[:user_team])
- @team.owner = current_user unless params[:owner]
- @team.path = @team.name.dup.parameterize if @team.name
-
- if @team.save
- redirect_to team_path(@team)
- else
- render action: :new
- end
- end
-
- # Get authored or assigned open merge requests
- def merge_requests
- projects
- @merge_requests = MergeRequest.of_user_team(user_team)
- @merge_requests = FilterContext.new(@merge_requests, params).execute
- @merge_requests = @merge_requests.recent.page(params[:page]).per(20)
- end
-
- # Get only assigned issues
- def issues
- projects
- @issues = Issue.of_user_team(user_team)
- @issues = FilterContext.new(@issues, params).execute
- @issues = @issues.recent.page(params[:page]).per(20)
- @issues = @issues.includes(:author, :project)
- end
-
- protected
-
- def projects
- @projects ||= user_team.projects.sorted_by_activity
- end
-
- def user_team
- @team ||= current_user.authorized_teams.find_by_path(params[:id])
- end
-end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index e027057fe65..4947c33f959 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,7 +1,11 @@
class UsersController < ApplicationController
+ layout 'navless'
+
def show
@user = User.find_by_username!(params[:username])
@projects = @user.authorized_projects.where('projects.id in (?)', current_user.authorized_projects.map(&:id))
@events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20)
+
+ @title = @user.name
end
end
diff --git a/app/controllers/users_groups_controller.rb b/app/controllers/users_groups_controller.rb
new file mode 100644
index 00000000000..749da1e1413
--- /dev/null
+++ b/app/controllers/users_groups_controller.rb
@@ -0,0 +1,41 @@
+class UsersGroupsController < ApplicationController
+ before_filter :group
+
+ # Authorize
+ before_filter :authorize_admin_group!
+
+ layout 'group'
+
+ def create
+ @group.add_users(params[:user_ids].split(','), params[:group_access])
+
+ redirect_to members_group_path(@group), notice: 'Users were successfully added.'
+ end
+
+ def update
+ @member = @group.users_groups.find(params[:id])
+ @member.update_attributes(params[:users_group])
+ end
+
+ def destroy
+ @users_group = @group.users_groups.find(params[:id])
+ @users_group.destroy
+
+ respond_to do |format|
+ format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' }
+ format.js { render nothing: true }
+ end
+ end
+
+ protected
+
+ def group
+ @group ||= Group.find_by_path(params[:group_id])
+ end
+
+ def authorize_admin_group!
+ unless can?(current_user, :manage_group, group)
+ return render_404
+ end
+ end
+end
diff --git a/app/controllers/wikis_controller.rb b/app/controllers/wikis_controller.rb
deleted file mode 100644
index 69280291003..00000000000
--- a/app/controllers/wikis_controller.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-class WikisController < ProjectResourceController
- before_filter :authorize_read_wiki!
- before_filter :authorize_write_wiki!, only: [:edit, :create, :history]
- before_filter :authorize_admin_wiki!, only: :destroy
-
- def pages
- @wiki_pages = @project.wikis.group(:slug).ordered
- end
-
- def show
- @most_recent_wiki = @project.wikis.where(slug: params[:id]).ordered.first
- if params[:version_id]
- @wiki = @project.wikis.find(params[:version_id])
- else
- @wiki = @most_recent_wiki
- end
-
- if @wiki
- render 'show'
- else
- if can?(current_user, :write_wiki, @project)
- @wiki = @project.wikis.new(slug: params[:id])
- render 'edit'
- else
- render 'empty'
- end
- end
- end
-
- def edit
- @wiki = @project.wikis.where(slug: params[:id]).ordered.first
- @wiki = Wiki.regenerate_from @wiki
- end
-
- def create
- @wiki = @project.wikis.new(params[:wiki])
- @wiki.user = current_user
-
- respond_to do |format|
- if @wiki.save
- format.html { redirect_to [@project, @wiki], notice: 'Wiki was successfully updated.' }
- else
- format.html { render action: "edit" }
- end
- end
- end
-
- def history
- @wiki_pages = @project.wikis.where(slug: params[:id]).ordered
- end
-
- def destroy
- @wikis = @project.wikis.where(slug: params[:id]).delete_all
-
- respond_to do |format|
- format.html { redirect_to project_wiki_path(@project, :index), notice: "Page was successfully deleted" }
- end
- end
-end
diff --git a/app/decorators/application_decorator.rb b/app/decorators/application_decorator.rb
deleted file mode 100644
index 3023699e700..00000000000
--- a/app/decorators/application_decorator.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-class ApplicationDecorator < Draper::Base
- # Lazy Helpers
- # PRO: Call Rails helpers without the h. proxy
- # ex: number_to_currency(model.price)
- # CON: Add a bazillion methods into your decorator's namespace
- # and probably sacrifice performance/memory
- #
- # Enable them by uncommenting this line:
- # lazy_helpers
-
- # Shared Decorations
- # Consider defining shared methods common to all your models.
- #
- # Example: standardize the formatting of timestamps
- #
- # def formatted_timestamp(time)
- # h.content_tag :span, time.strftime("%a %m/%d/%y"),
- # class: 'timestamp'
- # end
- #
- # def created_at
- # formatted_timestamp(model.created_at)
- # end
- #
- # def updated_at
- # formatted_timestamp(model.updated_at)
- # end
-end
diff --git a/app/decorators/commit_decorator.rb b/app/decorators/commit_decorator.rb
deleted file mode 100644
index a066b2e4a26..00000000000
--- a/app/decorators/commit_decorator.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-class CommitDecorator < ApplicationDecorator
- decorates :commit
-
- # Returns a string describing the commit for use in a link title
- #
- # Example
- #
- # "Commit: Alex Denisov - Project git clone panel"
- def link_title
- "Commit: #{author_name} - #{title}"
- end
-
- # Returns the commits title.
- #
- # Usually, the commit title is the first line of the commit message.
- # In case this first line is longer than 80 characters, it is cut off
- # after 70 characters and ellipses (`&hellp;`) are appended.
- def title
- title = safe_message
-
- return no_commit_message if title.blank?
-
- title_end = title.index(/\n/)
- if (!title_end && title.length > 80) || (title_end && title_end > 80)
- title[0..69] << "&hellip;".html_safe
- else
- title.split(/\n/, 2).first
- end
- end
-
- # Returns the commits description
- #
- # cut off, ellipses (`&hellp;`) are prepended to the commit message.
- def description
- description = safe_message
-
- title_end = description.index(/\n/)
- if (!title_end && description.length > 80) || (title_end && title_end > 80)
- "&hellip;".html_safe << description[70..-1]
- else
- description.split(/\n/, 2)[1].try(:chomp)
- end
- end
-
- # Returns a link to the commit author. If the author has a matching user and
- # is a member of the current @project it will link to the team member page.
- # Otherwise it will link to the author email as specified in the commit.
- #
- # options:
- # avatar: true will prepend the avatar image
- # size: size of the avatar image in px
- def author_link(options = {})
- person_link(options.merge source: :author)
- end
-
- # Just like #author_link but for the committer.
- def committer_link(options = {})
- person_link(options.merge source: :committer)
- end
-
- protected
-
- def no_commit_message
- "--no commit message"
- end
-
- # Private: Returns a link to a person. If the person has a matching user and
- # is a member of the current @project it will link to the team member page.
- # Otherwise it will link to the person email as specified in the commit.
- #
- # options:
- # source: one of :author or :committer
- # avatar: true will prepend the avatar image
- # size: size of the avatar image in px
- def person_link(options = {})
- source_name = send "#{options[:source]}_name".to_sym
- source_email = send "#{options[:source]}_email".to_sym
- text = if options[:avatar]
- avatar = h.image_tag h.gravatar_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>}
- else
- source_name
- end
- team_member = @project.try(:team_member_by_name_or_email, source_name, source_email)
-
- if team_member.nil?
- h.mail_to source_email, text.html_safe, class: "commit-#{options[:source]}-link"
- else
- h.link_to text, h.project_team_member_path(@project, team_member), class: "commit-#{options[:source]}-link"
- end
- end
-end
diff --git a/app/decorators/event_decorator.rb b/app/decorators/event_decorator.rb
deleted file mode 100644
index 1b0ad0da28f..00000000000
--- a/app/decorators/event_decorator.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-class EventDecorator < ApplicationDecorator
- decorates :event
-
- def feed_title
- if self.issue?
- "#{self.author_name} #{self.action_name} issue ##{self.target_id}: #{self.issue_title} at #{self.project.name}"
- elsif self.merge_request?
- "#{self.author_name} #{self.action_name} MR ##{self.target_id}: #{self.merge_request_title} at #{self.project.name}"
- elsif self.push?
- "#{self.author_name} #{self.push_action_name} #{self.ref_type} #{self.ref_name} at #{self.project.name}"
- elsif self.membership_changed?
- "#{self.author_name} #{self.action_name} #{self.project.name}"
- else
- ""
- end
- end
-
- def feed_url
- if self.issue?
- h.project_issue_url(self.project, self.issue)
- elsif self.merge_request?
- h.project_merge_request_url(self.project, self.merge_request)
-
- elsif self.push?
- if self.push_with_commits?
- if self.commits_count > 1
- h.project_compare_url(self.project, :from => self.parent_commit.id, :to => self.last_commit.id)
- else
- h.project_commit_url(self.project, :id => self.last_commit.id)
- end
- else
- h.project_commits_url(self.project, self.ref_name)
- end
- end
- end
-
- def feed_summary
- if self.issue?
- h.render "events/event_issue", issue: self.issue
- elsif self.push?
- h.render "events/event_push", event: self
- end
- end
-end
diff --git a/app/decorators/tree_decorator.rb b/app/decorators/tree_decorator.rb
deleted file mode 100644
index 0e760f97dee..00000000000
--- a/app/decorators/tree_decorator.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-class TreeDecorator < ApplicationDecorator
- decorates :tree
-
- def breadcrumbs(max_links = 2)
- if path
- part_path = ""
- parts = path.split("\/")
-
- yield('..', nil) if parts.count > max_links
-
- parts.each do |part|
- part_path = File.join(part_path, part) unless part_path.empty?
- part_path = part if part_path.empty?
-
- next unless parts.last(2).include?(part) if parts.count > max_links
- yield(part, h.tree_join(ref, part_path))
- end
- end
- end
-
- def up_dir?
- path.present?
- end
-
- def up_dir_path
- file = File.join(path, "..")
- h.tree_join(ref, file)
- end
-
- def readme
- @readme ||= contents.find { |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i }
- end
-end
diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb
deleted file mode 100644
index b781f237352..00000000000
--- a/app/decorators/user_decorator.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-class UserDecorator < ApplicationDecorator
- decorates :user
-
- def avatar_image size = 16
- h.image_tag h.gravatar_icon(self.email, size), class: "avatar #{"s#{size}"}", width: size
- end
-
- def tm_of(project)
- project.team_member_by_id(self.id)
- end
-
- def name_with_email
- "#{name} (#{email})"
- end
-end
diff --git a/app/helpers/admin/teams/members_helper.rb b/app/helpers/admin/teams/members_helper.rb
deleted file mode 100644
index 58b9f1896c4..00000000000
--- a/app/helpers/admin/teams/members_helper.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Admin::Teams::MembersHelper
- def member_since(team, member)
- team.user_team_user_relationships.find_by_user_id(member).created_at
- end
-end
diff --git a/app/helpers/admin/teams/projects_helper.rb b/app/helpers/admin/teams/projects_helper.rb
deleted file mode 100644
index b97cc403337..00000000000
--- a/app/helpers/admin/teams/projects_helper.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Admin::Teams::ProjectsHelper
- def assigned_since(team, project)
- team.user_team_project_relationships.find_by_project_id(project).created_at
- end
-end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 196105f0119..7e5c10fee05 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -2,6 +2,24 @@ require 'digest/md5'
require 'uri'
module ApplicationHelper
+ COLOR_SCHEMES = {
+ 1 => 'white',
+ 2 => 'dark',
+ 3 => 'solarized-dark',
+ 4 => 'monokai',
+ }
+ COLOR_SCHEMES.default = 'white'
+
+ # Helper method to access the COLOR_SCHEMES
+ #
+ # The keys are the `color_scheme_ids`
+ # The values are the `name` of the scheme.
+ #
+ # The preview images are `name-scheme-preview.png`
+ # The stylesheets should use the css class `.name`
+ def color_schemes
+ COLOR_SCHEMES.freeze
+ end
# Check if a particular controller is the current one
#
@@ -17,7 +35,7 @@ module ApplicationHelper
args.any? { |v| v.to_s.downcase == controller.controller_name }
end
- # Check if a partcular action is the current one
+ # Check if a particular action is the current one
#
# args - One or more action names to check
#
@@ -37,7 +55,7 @@ module ApplicationHelper
if !Gitlab.config.gravatar.enabled || user_email.blank?
'no_avatar.png'
else
- gravatar_url = request.ssl? ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
+ gravatar_url = request.ssl? || gitlab_config.https ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
user_email.strip!
sprintf gravatar_url, hash: Digest::MD5.hexdigest(user_email.downcase), size: size
end
@@ -72,13 +90,14 @@ module ApplicationHelper
end
def search_autocomplete_source
- projects = current_user.authorized_projects.map { |p| { label: "project: #{p.name_with_namespace}", url: project_path(p) } }
- groups = current_user.authorized_groups.map { |group| { label: "group: #{group.name}", url: group_path(group) } }
- teams = current_user.authorized_teams.map { |team| { label: "team: #{team.name}", url: team_path(team) } }
+ return unless current_user
+
+ projects = current_user.authorized_projects.map { |p| { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } }
+ groups = current_user.authorized_groups.map { |group| { label: "group: #{simple_sanitize(group.name)}", url: group_path(group) } }
default_nav = [
{ label: "My Profile", url: profile_path },
- { label: "My SSH Keys", url: keys_path },
+ { label: "My SSH Keys", url: profile_keys_path },
{ label: "My Dashboard", url: root_path },
{ label: "Admin Section", url: admin_root_path },
]
@@ -96,17 +115,19 @@ module ApplicationHelper
]
project_nav = []
- if @project && @project.repository && @project.repository.root_ref
+ if @project && @project.repository.exists? && @project.repository.root_ref
project_nav = [
- { label: "#{@project.name_with_namespace} - Issues", url: project_issues_path(@project) },
- { label: "#{@project.name_with_namespace} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) },
- { label: "#{@project.name_with_namespace} - Merge Requests", url: project_merge_requests_path(@project) },
- { label: "#{@project.name_with_namespace} - Milestones", url: project_milestones_path(@project) },
- { label: "#{@project.name_with_namespace} - Snippets", url: project_snippets_path(@project) },
- { label: "#{@project.name_with_namespace} - Team", url: project_team_index_path(@project) },
- { label: "#{@project.name_with_namespace} - Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) },
- { label: "#{@project.name_with_namespace} - Wall", url: wall_project_path(@project) },
- { label: "#{@project.name_with_namespace} - Wiki", url: project_wikis_path(@project) },
+ { label: "#{simple_sanitize(@project.name_with_namespace)} - Files", url: project_tree_path(@project, @ref || @project.repository.root_ref) },
+ { label: "#{simple_sanitize(@project.name_with_namespace)} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) },
+ { label: "#{simple_sanitize(@project.name_with_namespace)} - Network", url: project_network_path(@project, @ref || @project.repository.root_ref) },
+ { label: "#{simple_sanitize(@project.name_with_namespace)} - Graph", url: project_graph_path(@project, @ref || @project.repository.root_ref) },
+ { label: "#{simple_sanitize(@project.name_with_namespace)} - Issues", url: project_issues_path(@project) },
+ { label: "#{simple_sanitize(@project.name_with_namespace)} - Merge Requests", url: project_merge_requests_path(@project) },
+ { label: "#{simple_sanitize(@project.name_with_namespace)} - Milestones", url: project_milestones_path(@project) },
+ { label: "#{simple_sanitize(@project.name_with_namespace)} - Snippets", url: project_snippets_path(@project) },
+ { label: "#{simple_sanitize(@project.name_with_namespace)} - Team", url: project_team_index_path(@project) },
+ { label: "#{simple_sanitize(@project.name_with_namespace)} - Wall", url: project_wall_path(@project) },
+ { label: "#{simple_sanitize(@project.name_with_namespace)} - Wiki", url: project_wikis_path(@project) },
]
end
@@ -119,25 +140,29 @@ module ApplicationHelper
Emoji.names.to_s
end
- def ldap_enable?
- Devise.omniauth_providers.include?(:ldap)
- end
-
def app_theme
Gitlab::Theme.css_class_by_id(current_user.try(:theme_id))
end
def user_color_scheme_class
- current_user.dark_scheme ? :black : :white
+ COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end
+ # Define whenever show last push event
+ # with suggestion to create MR
def show_last_push_widget?(event)
- event &&
- event.last_push_to_non_root? &&
- !event.rm_ref? &&
- event.project &&
- event.project.repository &&
- event.project.merge_requests_enabled
+ # Skip if event is not about added or modified non-master branch
+ return false unless event && event.last_push_to_non_root? && !event.rm_ref?
+
+ project = event.project
+
+ # Skip if project repo is empty or MR disabled
+ return false unless project && !project.empty_repo? && project.merge_requests_enabled
+
+ # Skip if user already created appropriate MR
+ return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
+
+ true
end
def hexdigest(string)
@@ -145,9 +170,8 @@ module ApplicationHelper
end
def project_last_activity project
- activity = project.last_activity
- if activity && activity.created_at
- time_ago_in_words(activity.created_at) + " ago"
+ if project.last_activity_at
+ time_ago_in_words(project.last_activity_at) + " ago"
else
"Never"
end
@@ -159,8 +183,65 @@ module ApplicationHelper
alt: "Sign in with #{provider.to_s.titleize}")
end
+ def simple_sanitize str
+ sanitize(str, tags: %w(a span))
+ end
+
def image_url(source)
- root_url + path_to_image(source)
+ # prevent relative_root_path being added twice (it's part of root_url and path_to_image)
+ root_url.sub(/#{root_path}$/, path_to_image(source))
end
+
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
+
+ [namespace, controller.controller_name, controller.action_name].compact.join(":")
+ end
+
+ # shortcut for gitlab config
+ def gitlab_config
+ Gitlab.config.gitlab
+ end
+
+ # shortcut for gitlab extra config
+ def extra_config
+ Gitlab.config.extra
+ end
+
+ def public_icon
+ content_tag :i, nil, class: 'icon-globe cblue'
+ end
+
+ def private_icon
+ content_tag :i, nil, class: 'icon-lock cgreen'
+ end
+
+ def search_placeholder
+ if @project && @project.persisted?
+ "Search in this project"
+ elsif @group && @group.persisted?
+ "Search in this group"
+ else
+ "Search"
+ end
+ end
+
+ def first_line(str)
+ lines = str.split("\n")
+ line = lines.first
+ line += "..." if lines.size > 1
+ line
+ end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 6d2ce2feea3..f8f84ff8b62 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -1,58 +1,47 @@
module CommitsHelper
- def identification_type(line)
- if line[0] == "+"
- "new"
- elsif line[0] == "-"
- "old"
- else
- nil
- end
+ # Returns a link to the commit author. If the author has a matching user and
+ # is a member of the current @project it will link to the team member page.
+ # Otherwise it will link to the author email as specified in the commit.
+ #
+ # options:
+ # avatar: true will prepend the avatar image
+ # size: size of the avatar image in px
+ def commit_author_link(commit, options = {})
+ commit_person_link(commit, options.merge(source: :author))
end
- def build_line_anchor(diff, line_new, line_old)
- "#{hexdigest(diff.new_path)}_#{line_old}_#{line_new}"
+ # Just like #author_link but for the committer.
+ def commit_committer_link(commit, options = {})
+ commit_person_link(commit, options.merge(source: :committer))
end
def each_diff_line(diff, index)
- diff_arr = diff.diff.lines.to_a
-
- line_old = 1
- line_new = 1
- type = nil
-
- lines_arr = ::Gitlab::InlineDiff.processing diff_arr
- lines_arr.each do |line|
- next if line.match(/^\-\-\- \/dev\/null/)
- next if line.match(/^\+\+\+ \/dev\/null/)
- next if line.match(/^\-\-\- a/)
- next if line.match(/^\+\+\+ b/)
-
- full_line = html_escape(line.gsub(/\n/, ''))
- full_line = ::Gitlab::InlineDiff.replace_markers full_line
-
- if line.match(/^@@ -/)
- type = "match"
-
- line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
- line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
+ Gitlab::DiffParser.new(diff).each do |full_line, type, line_code, line_new, line_old|
+ yield(full_line, type, line_code, line_new, line_old)
+ end
+ end
- next if line_old == 1 && line_new == 1 #top of file
- yield(full_line, type, nil, nil, nil)
- next
- else
- type = identification_type(line)
- line_code = build_line_anchor(diff, line_new, line_old)
- yield(full_line, type, line_code, line_new, line_old)
- end
+ def each_diff_line_near(diff, index, expected_line_code)
+ max_number_of_lines = 16
+ prev_match_line = nil
+ prev_lines = []
- if line[0] == "+"
- line_new += 1
- elsif line[0] == "-"
- line_old += 1
+ each_diff_line(diff, index) do |full_line, type, line_code, line_new, line_old|
+ line = [full_line, type, line_code, line_new, line_old]
+ if line_code != expected_line_code
+ if type == "match"
+ prev_lines.clear
+ prev_match_line = line
+ else
+ prev_lines.push(line)
+ prev_lines.shift if prev_lines.length >= max_number_of_lines
+ end
else
- line_new += 1
- line_old += 1
+ yield(prev_match_line) if !prev_match_line.nil?
+ prev_lines.each { |ln| yield(ln) }
+ yield(line)
+ break
end
end
end
@@ -67,10 +56,9 @@ module CommitsHelper
end
end
- def commit_to_html commit
- if commit.model
- escape_javascript(render 'commits/commit', commit: commit)
- end
+ def commit_to_html(commit, project, inline = true)
+ template = inline ? "inline_commit" : "commit"
+ escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
end
def diff_line_content(line)
@@ -80,4 +68,63 @@ module CommitsHelper
line
end
end
+
+ # Breadcrumb links for a Project and, if applicable, a tree path
+ def commits_breadcrumbs
+ return unless @project && @ref
+
+ # Add the root project link and the arrow icon
+ crumbs = content_tag(:li) do
+ content_tag(:span, nil, class: 'arrow') +
+ link_to(@project.name, project_commits_path(@project, @ref))
+ end
+
+ if @path
+ parts = @path.split('/')
+
+ parts.each_with_index do |part, i|
+ crumbs += content_tag(:span, ' / ', class: 'divider')
+ crumbs += content_tag(:li) do
+ # The text is just the individual part, but the link needs all the parts before it
+ link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/')))
+ end
+ end
+ end
+
+ crumbs.html_safe
+ end
+
+ protected
+
+ # Private: Returns a link to a person. If the person has a matching user and
+ # is a member of the current @project it will link to the team member page.
+ # Otherwise it will link to the person email as specified in the commit.
+ #
+ # options:
+ # source: one of :author or :committer
+ # avatar: true will prepend the avatar image
+ # size: size of the avatar image in px
+ def commit_person_link(commit, options = {})
+ source_name = commit.send "#{options[:source]}_name".to_sym
+ source_email = commit.send "#{options[:source]}_email".to_sym
+ text = if options[:avatar]
+ avatar = image_tag(gravatar_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>}
+ else
+ source_name
+ end
+
+ user = User.where('name like ? or email like ?', source_name, source_email).first
+
+ options = {
+ class: "commit-#{options[:source]}-link has_tooltip",
+ data: { :'original-title' => sanitize(source_email) }
+ }
+
+ if user.nil?
+ mail_to(source_email, text.html_safe, options)
+ else
+ link_to(text.html_safe, user_path(user), options)
+ end
+ end
end
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
new file mode 100644
index 00000000000..ea2540bf385
--- /dev/null
+++ b/app/helpers/compare_helper.rb
@@ -0,0 +1,13 @@
+module CompareHelper
+ def compare_to_mr_button?
+ params[:from].present? && params[:to].present? &&
+ @repository.branch_names.include?(params[:from]) &&
+ @repository.branch_names.include?(params[:to]) &&
+ params[:from] != params[:to] &&
+ !@refs_are_same
+ end
+
+ def compare_mr_path
+ new_project_merge_request_path(@project, merge_request: {source_branch: params[:to], target_branch: params[:from]})
+ end
+end
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index c759dffa16e..35c7bcbd2cf 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -1,5 +1,5 @@
module DashboardHelper
- def dashboard_filter_path(entity, options={})
+ def filter_path(entity, options={})
exist_opts = {
status: params[:status],
project_id: params[:project_id],
@@ -7,12 +7,9 @@ module DashboardHelper
options = exist_opts.merge(options)
- case entity
- when 'issue' then
- issues_dashboard_path(options)
- when 'merge_request'
- merge_requests_dashboard_path(options)
- end
+ path = request.path
+ path << "?#{options.to_param}"
+ path
end
def entities_per_project project, entity
@@ -27,6 +24,6 @@ module DashboardHelper
items.opened
end
- items.where(assignee_id: current_user.id).count
+ items.cared(current_user).count
end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 38374e3394d..cd8761a6113 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -28,7 +28,7 @@ module EventsHelper
end
content_tag :div, class: "filter_icon #{inactive}" do
- link_to dashboard_path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do
+ link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do
content_tag :i, nil, class: icon_for_event[key]
end
end
@@ -42,4 +42,89 @@ module EventsHelper
EventFilter.team => "icon-user",
}
end
+
+ def event_feed_title(event)
+ if event.issue?
+ "#{event.author_name} #{event.action_name} issue ##{event.target_id}: #{event.issue_title} at #{event.project_name}"
+ elsif event.merge_request?
+ "#{event.author_name} #{event.action_name} MR ##{event.target_id}: #{event.merge_request_title} at #{event.project_name}"
+ elsif event.push?
+ "#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}"
+ elsif event.membership_changed?
+ "#{event.author_name} #{event.action_name} #{event.project_name}"
+ elsif event.note?
+ "#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_id} at #{event.project_name}"
+ else
+ ""
+ end
+ end
+
+ def event_feed_url(event)
+ if event.issue?
+ project_issue_url(event.project, event.issue)
+ elsif event.merge_request?
+ project_merge_request_url(event.project, event.merge_request)
+
+ elsif event.push?
+ if event.push_with_commits?
+ if event.commits_count > 1
+ project_compare_url(event.project, from: event.commit_from, to: event.commit_to)
+ else
+ project_commit_url(event.project, id: event.commit_to)
+ end
+ else
+ project_commits_url(event.project, event.ref_name)
+ end
+ end
+ end
+
+ def event_feed_summary(event)
+ if event.issue?
+ render "events/event_issue", issue: event.issue
+ elsif event.push?
+ render "events/event_push", event: event
+ end
+ end
+
+ def event_note_target_path(event)
+ if event.note? && event.note_commit?
+ project_commit_path(event.project, event.note_target)
+ else
+ url_for([event.project, event.note_target])
+ end
+ end
+
+ def event_note_title_html(event)
+ if event.note_target
+ if event.note_commit?
+ link_to project_commit_path(event.project, event.note_commit_id), class: "commit_short_id" do
+ "#{event.note_target_type} #{event.note_short_commit_id}"
+ end
+ elsif event.note_project_snippet?
+ link_to(project_snippet_path(event.project, event.note_target)) do
+ content_tag :strong do
+ "#{event.note_target_type} ##{truncate event.note_target_id}"
+ end
+ end
+ else
+ link_to event_note_target_path(event) do
+ content_tag :strong do
+ "#{event.note_target_type} ##{truncate event.note_target_iid}"
+ end
+ end
+ end
+ elsif event.wall_note?
+ link_to 'wall', project_wall_path(event.project)
+ else
+ content_tag :strong do
+ "(deleted)"
+ end
+ end
+ end
+
+ def event_note(text)
+ text = first_line(text)
+ text = truncate(text, length: 150)
+ sanitize(markdown(text), tags: %w(a img b pre p))
+ end
end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 1a3d34eb886..375f8861dae 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -49,4 +49,12 @@ module GitlabMarkdownHelper
@markdown.render(text).html_safe
end
+
+ def render_wiki_content(wiki_page)
+ if wiki_page.format == :markdown
+ markdown(wiki_page.content)
+ else
+ wiki_page.formatted_content.html_safe
+ end
+ end
end
diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb
new file mode 100644
index 00000000000..7cb1b6f8d1a
--- /dev/null
+++ b/app/helpers/graph_helper.rb
@@ -0,0 +1,16 @@
+module GraphHelper
+ def get_refs(repo, commit)
+ refs = ""
+ refs += commit.ref_names(repo).join(" ")
+
+ # append note count
+ refs += "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0
+
+ refs
+ end
+
+ def parents_zip_spaces(parents, parent_spaces)
+ ids = parents.map { |p| p.id }
+ ids.zip(parent_spaces)
+ end
+end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 283119bc24c..8573c59dc94 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -1,17 +1,5 @@
module GroupsHelper
- def group_filter_path(entity, options={})
- exist_opts = {
- status: params[:status],
- project_id: params[:project_id],
- }
-
- options = exist_opts.merge(options)
-
- case entity
- when 'issue' then
- issues_group_path(@group, options)
- when 'merge_request'
- merge_requests_group_path(@group, options)
- end
+ def remove_user_from_group_message(group, user)
+ "You are going to remove #{user.name} from #{group.name} Group. Are you sure?"
end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 2825787fd2f..5977c9cbae2 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -1,43 +1,62 @@
module IssuesHelper
- def project_issues_filter_path project, params = {}
- params[:f] ||= cookies['issue_filter']
- project_issues_path project, params
- end
-
def issue_css_classes issue
classes = "issue"
- classes << " closed" if issue.closed
+ classes << " closed" if issue.closed?
classes << " today" if issue.today?
classes
end
- def issue_tags
- @project.issues.tag_counts_on(:labels).map(&:name)
- end
-
# Returns an OpenStruct object suitable for use by <tt>options_from_collection_for_select</tt>
# to allow filtering issues by an unassigned User or Milestone
def unassigned_filter
# Milestone uses :title, Issue uses :name
- OpenStruct.new(id: 0, title: 'Unspecified', name: 'Unassigned')
+ OpenStruct.new(id: 0, title: 'None (backlog)', name: 'Unassigned')
+ end
+
+ def url_for_project_issues
+ return "" if @project.nil?
+
+ if @project.used_default_issues_tracker?
+ project_issues_path(@project)
+ else
+ url = Gitlab.config.issues_tracker[@project.issues_tracker]["project_url"]
+ url.gsub(':project_id', @project.id.to_s)
+ .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
+ end
end
- def issues_filter
- {
- all: "all",
- closed: "closed",
- to_me: "assigned-to-me",
- open: "open"
- }
+ def url_for_new_issue
+ return "" if @project.nil?
+
+ if @project.used_default_issues_tracker?
+ url = new_project_issue_path project_id: @project
+ else
+ url = Gitlab.config.issues_tracker[@project.issues_tracker]["new_issue_url"]
+ url.gsub(':project_id', @project.id.to_s)
+ .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
+ end
end
- def labels_autocomplete_source
- labels = @project.issues_labels.order('count DESC')
- labels = labels.map{ |l| { label: l.name, value: l.name } }
- labels.to_json
+ def url_for_issue(issue_iid)
+ return "" if @project.nil?
+
+ if @project.used_default_issues_tracker?
+ url = project_issue_url project_id: @project, id: issue_iid
+ else
+ url = Gitlab.config.issues_tracker[@project.issues_tracker]["issues_url"]
+ url.gsub(':id', issue_iid.to_s)
+ .gsub(':project_id', @project.id.to_s)
+ .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
+ end
end
- def issues_active_milestones
- @project.milestones.active.order("id desc").all
+ def title_for_issue(issue_iid)
+ return "" if @project.nil?
+
+ if @project.used_default_issues_tracker? && issue = @project.issues.where(iid: issue_iid).first
+ issue.title
+ else
+ ""
+ end
end
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
new file mode 100644
index 00000000000..9a6aea85ee9
--- /dev/null
+++ b/app/helpers/labels_helper.rb
@@ -0,0 +1,28 @@
+module LabelsHelper
+ def issue_label_names
+ @project.issues_labels.map(&:name)
+ end
+
+ def labels_autocomplete_source
+ labels = @project.issues_labels
+ labels = labels.map{ |l| { label: l.name, value: l.name } }
+ labels.to_json
+ end
+
+ def label_css_class(name)
+ klass = Gitlab::IssuesLabels
+
+ case name
+ when *klass.warning_labels
+ 'label-warning'
+ when *klass.neutral_labels
+ 'label-inverse'
+ when *klass.positive_labels
+ 'label-success'
+ when *klass.important_labels
+ 'label-important'
+ else
+ 'label-info'
+ end
+ end
+end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index ca0a89c3749..4ba48aa4339 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -2,22 +2,43 @@ module MergeRequestsHelper
def new_mr_path_from_push_event(event)
new_project_merge_request_path(
event.project,
- merge_request: {
- source_branch: event.branch_name,
- target_branch: event.project.repository.root_ref,
- title: event.branch_name.titleize
- }
+ new_mr_from_push_event(event, event.project)
)
end
+ def new_mr_path_for_fork_from_push_event(event)
+ new_project_merge_request_path(
+ event.project,
+ new_mr_from_push_event(event, event.project.forked_from_project)
+ )
+ end
+
+ def new_mr_from_push_event(event, target_project)
+ return :merge_request => {
+ source_project_id: event.project.id,
+ target_project_id: target_project.id,
+ source_branch: event.branch_name,
+ target_branch: target_project.repository.root_ref,
+ title: event.branch_name.titleize
+ }
+ end
+
def mr_css_classes mr
- classes = "merge_request"
- classes << " closed" if mr.closed
+ classes = "merge-request"
+ classes << " closed" if mr.closed?
classes << " merged" if mr.merged?
classes
end
def ci_build_details_path merge_request
- merge_request.project.gitlab_ci_service.build_page(merge_request.last_commit.sha)
+ merge_request.source_project.gitlab_ci_service.build_page(merge_request.last_commit.sha)
+ end
+
+ def merge_path_description(merge_request, separator)
+ if merge_request.for_fork?
+ "Project:Branches: #{@merge_request.source_project.path_with_namespace}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}"
+ else
+ "Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}"
+ end
end
end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index 6d0c6c98191..dc88e178360 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -1,20 +1,12 @@
module NamespacesHelper
def namespaces_options(selected = :current_user, scope = :default)
- if current_user.admin
- groups = Group.all
- users = Namespace.root
- else
- groups = current_user.owned_groups.select {|n| n.type == 'Group'}
- users = current_user.namespaces.reject {|n| n.type == 'Group'}
- end
-
+ groups = current_user.owned_groups.select {|n| n.type == 'Group'}
+ users = current_user.namespaces.reject {|n| n.type == 'Group'}
- global_opts = ["Global", [['/', Namespace.global_id]] ]
- group_opts = ["Groups", groups.map {|g| [g.human_name, g.id]} ]
- users_opts = [ "Users", users.map {|u| [u.human_name, u.id]} ]
+ group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ]
+ users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ]
options = []
- options << global_opts if current_user.admin
options << group_opts
options << users_opts
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 7a0ed251aa8..a3ec4cca59d 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -1,8 +1,7 @@
module NotesHelper
# Helps to distinguish e.g. commit notes in mr notes list
def note_for_main_target?(note)
- note.for_wall? ||
- (@target_type.camelize == note.noteable_type && !note.for_diff_line?)
+ (@target_type.camelize == note.noteable_type && !note.for_diff_line?)
end
def note_target_fields
@@ -29,4 +28,11 @@ module NotesHelper
def loading_new_notes?
params[:loading_new].present?
end
+
+ def note_timestamp(note)
+ # Shows the created at time and the updated at time if different
+ ts = "#{time_ago_in_words(note.created_at)} ago"
+ ts << content_tag(:small, " (Edited #{time_ago_in_words(note.updated_at)} ago)") if note.updated_at != note.created_at
+ ts.html_safe
+ end
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
new file mode 100644
index 00000000000..ae3402b2617
--- /dev/null
+++ b/app/helpers/notifications_helper.rb
@@ -0,0 +1,13 @@
+module NotificationsHelper
+ def notification_icon(notification)
+ if notification.disabled?
+ content_tag :i, nil, class: 'icon-circle cred'
+ elsif notification.participating?
+ content_tag :i, nil, class: 'icon-circle cblue'
+ elsif notification.watch?
+ content_tag :i, nil, class: 'icon-circle cgreen'
+ else
+ content_tag :i, nil, class: 'icon-circle-blank cblue'
+ end
+ end
+end
diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb
new file mode 100644
index 00000000000..c0177dacbf8
--- /dev/null
+++ b/app/helpers/oauth_helper.rb
@@ -0,0 +1,19 @@
+module OauthHelper
+ def ldap_enabled?
+ Devise.omniauth_providers.include?(:ldap)
+ end
+
+ def default_providers
+ [:twitter, :github, :google_oauth2, :ldap]
+ end
+
+ def enabled_oauth_providers
+ Devise.omniauth_providers
+ end
+
+ def enabled_social_providers
+ enabled_oauth_providers.select do |name|
+ [:twitter, :github, :google_oauth2].include?(name.to_sym)
+ end
+ end
+end
diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb
index 80d67009f59..88d9f184d0e 100644
--- a/app/helpers/profile_helper.rb
+++ b/app/helpers/profile_helper.rb
@@ -4,4 +4,16 @@ module ProfileHelper
'active'
end
end
+
+ def show_profile_username_tab?
+ current_user.can_change_username?
+ end
+
+ def show_profile_social_tab?
+ Gitlab.config.omniauth.enabled && !current_user.ldap_user?
+ end
+
+ def show_profile_remove_tab?
+ Gitlab.config.gitlab.signup_enabled && !current_user.ldap_user?
+ end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 05303e86ae8..9071c688df1 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -1,12 +1,4 @@
module ProjectsHelper
- def grouper_project_members(project)
- @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
- end
-
- def grouper_project_teams(project)
- @project.user_team_project_relationships.sort_by(&:greatest_access).reverse.group_by(&:greatest_access)
- end
-
def remove_from_project_team_message(project, user)
"You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
end
@@ -25,7 +17,7 @@ module ProjectsHelper
end
def link_to_member(project, author, opts = {})
- default_opts = { avatar: true }
+ default_opts = { avatar: true, name: true, size: 16 }
opts = default_opts.merge(opts)
return "(deleted)" unless author
@@ -33,33 +25,98 @@ module ProjectsHelper
author_html = ""
# Build avatar image tag
- author_html << image_tag(gravatar_icon(author.try(:email)), width: 16, class: "lil_av") if opts[:avatar]
+ author_html << image_tag(gravatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
# Build name span tag
- author_html << content_tag(:span, sanitize(author.name), class: 'author')
+ author_html << content_tag(:span, sanitize(author.name), class: 'author') if opts[:name]
author_html = author_html.html_safe
- tm = project.team_member_by_id(author)
-
- if tm
- link_to author_html, project_team_member_path(project, tm.user_username), class: "author_link"
+ if opts[:name]
+ link_to(author_html, user_path(author), class: "author_link").html_safe
else
- author_html
- end.html_safe
- end
-
- def tm_path team_member
- project_team_member_path(@project, team_member)
+ link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => sanitize(author.name) } ).html_safe
+ end
end
def project_title project
if project.group
content_tag :span do
- link_to(project.group.name, group_path(project.group)) + " / " + project.name
+ link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name
end
else
project.name
end
end
+
+ def remove_project_message(project)
+ "You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?"
+ end
+
+ def project_nav_tabs
+ @nav_tabs ||= get_project_nav_tabs(@project, current_user)
+ end
+
+ def project_nav_tab?(name)
+ project_nav_tabs.include? name
+ end
+
+ def project_filter_path(options={})
+ exist_opts = {
+ state: params[:state],
+ scope: params[:scope],
+ label_name: params[:label_name],
+ milestone_id: params[:milestone_id],
+ }
+
+ options = exist_opts.merge(options)
+
+ path = request.path
+ path << "?#{options.to_param}"
+ path
+ end
+
+ def project_active_milestones
+ @project.milestones.active.order("id desc").all
+ end
+
+ private
+
+ def get_project_nav_tabs(project, current_user)
+ nav_tabs = [:home]
+
+ if !project.empty_repo? && can?(current_user, :download_code, project)
+ nav_tabs << [:files, :commits, :network, :graphs]
+ end
+
+ if project.repo_exists? && project.merge_requests_enabled
+ nav_tabs << :merge_requests
+ end
+
+ if can?(current_user, :admin_project, project)
+ nav_tabs << :settings
+ end
+
+ [:issues, :wiki, :wall, :snippets].each do |feature|
+ nav_tabs << feature if project.send :"#{feature}_enabled"
+ end
+
+ nav_tabs.flatten
+ end
+
+ def git_user_name
+ if current_user
+ current_user.name
+ else
+ "Your name"
+ end
+ end
+
+ def git_user_email
+ if current_user
+ current_user.email
+ else
+ "your@email.com"
+ end
+ end
end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 6a91d616722..b0abc2cae33 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -8,4 +8,12 @@ module SnippetsHelper
]
options_for_select(options)
end
+
+ def reliable_snippet_path(snippet)
+ if snippet.project_id?
+ project_snippet_path(snippet.project, snippet)
+ else
+ snippet_path(snippet)
+ end
+ end
end
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 5bd6de896ee..ce675872264 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -73,18 +73,16 @@ module TabHelper
end
def project_tab_class
- [:show, :files, :edit, :update].each do |action|
- return "active" if current_page?(controller: "projects", action: action, id: @project)
- end
+ return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
- if ['snippets', 'services', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name
+ if ['services', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name
"active"
end
end
def branches_tab_class
- if current_page?(branches_project_repository_path(@project)) ||
- current_controller?(:protected_branches) ||
+ if current_controller?(:protected_branches) ||
+ current_controller?(:branches) ||
current_page?(project_repository_path(@project))
'active'
end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 0f2b695e0ad..73d36d0801c 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -3,24 +3,20 @@ module TreeHelper
# their corresponding partials
#
# contents - A Grit::Tree object for the current tree
- def render_tree(contents)
+ def render_tree(tree)
# Render Folders before Files/Submodules
- folders, files = contents.partition { |v| v.kind_of?(Grit::Tree) }
+ folders, files, submodules = tree.trees, tree.blobs, tree.submodules
tree = ""
# Render folders if we have any
- tree += render partial: 'tree/tree_item', collection: folders, locals: {type: 'folder'} if folders.present?
-
- files.each do |f|
- if f.respond_to?(:url)
- # Object is a Submodule
- tree += render partial: 'tree/submodule_item', object: f
- else
- # Object is a Blob
- tree += render partial: 'tree/tree_item', object: f, locals: {type: 'file'}
- end
- end
+ tree += render partial: 'projects/tree/tree_item', collection: folders, locals: {type: 'folder'} if folders.present?
+
+ # Render files if we have any
+ tree += render partial: 'projects/tree/blob_item', collection: files, locals: {type: 'file'} if files.present?
+
+ # Render submodules if we have any
+ tree += render partial: 'projects/tree/submodule_item', collection: submodules if submodules.present?
tree.html_safe
end
@@ -43,16 +39,16 @@ module TreeHelper
#
# Returns boolean
def markup?(filename)
- filename.end_with?(*%w(.textile .rdoc .org .creole
- .mediawiki .rst .asciidoc .pod))
+ filename.downcase.end_with?(*%w(.textile .rdoc .org .creole
+ .mediawiki .rst .asciidoc .pod))
end
def gitlab_markdown?(filename)
- filename.end_with?(*%w(.mdown .md .markdown))
+ filename.downcase.end_with?(*%w(.mdown .md .markdown))
end
def plain_text_readme? filename
- filename == 'README'
+ filename =~ /^README(.txt)?$/i
end
# Simple shortcut to File.join
@@ -61,6 +57,8 @@ module TreeHelper
end
def allowed_tree_edit?
+ return false unless @repository.branch_names.include?(@ref)
+
if @project.protected_branch? @ref
can?(current_user, :push_code_to_protected_branches, @project)
else
@@ -68,28 +66,29 @@ module TreeHelper
end
end
- # Breadcrumb links for a Project and, if applicable, a tree path
- def breadcrumbs
- return unless @project && @ref
+ def tree_breadcrumbs(tree, max_links = 2)
+ if tree.path
+ part_path = ""
+ parts = tree.path.split("\/")
- # Add the root project link and the arrow icon
- crumbs = content_tag(:li) do
- content_tag(:span, nil, class: 'arrow') +
- link_to(@project.name, project_commits_path(@project, @ref))
- end
+ yield('..', nil) if parts.count > max_links
- if @path
- parts = @path.split('/')
+ parts.each do |part|
+ part_path = File.join(part_path, part) unless part_path.empty?
+ part_path = part if part_path.empty?
- parts.each_with_index do |part, i|
- crumbs += content_tag(:span, '/', class: 'divider')
- crumbs += content_tag(:li) do
- # The text is just the individual part, but the link needs all the parts before it
- link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/')))
- end
+ next unless parts.last(2).include?(part) if parts.count > max_links
+ yield(part, tree_join(tree.ref, part_path))
end
end
+ end
+
+ def up_dir_path tree
+ file = File.join(tree.path, "..")
+ tree_join(tree.ref, file)
+ end
- crumbs.html_safe
+ def leave_edit_message
+ "Leave edit mode?\nAll unsaved changes will be lost."
end
end
diff --git a/app/helpers/user_teams_helper.rb b/app/helpers/user_teams_helper.rb
deleted file mode 100644
index 2055bb3c8bc..00000000000
--- a/app/helpers/user_teams_helper.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module UserTeamsHelper
- def team_filter_path(entity, options={})
- exist_opts = {
- status: params[:status],
- project_id: params[:project_id],
- }
-
- options = exist_opts.merge(options)
-
- case entity
- when 'issue' then
- issues_team_path(@team, options)
- when 'merge_request'
- merge_requests_team_path(@team, options)
- end
- end
-
- def grouped_user_team_members(team)
- team.user_team_user_relationships.sort_by(&:permission).reverse.group_by(&:permission)
- end
-
- def remove_from_user_team_message(team, member)
- "You are going to remove #{member.name} from #{team.name}. Are you sure?"
- end
-
-end
diff --git a/app/mailers/emails/groups.rb b/app/mailers/emails/groups.rb
new file mode 100644
index 00000000000..2e9d28981e3
--- /dev/null
+++ b/app/mailers/emails/groups.rb
@@ -0,0 +1,11 @@
+module Emails
+ module Groups
+ def group_access_granted_email(user_group_id)
+ @membership = UsersGroup.find(user_group_id)
+ @group = @membership.group
+
+ mail(to: @membership.user.email,
+ subject: subject("access to group was granted"))
+ end
+ end
+end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
new file mode 100644
index 00000000000..6eda88c7921
--- /dev/null
+++ b/app/mailers/emails/issues.rb
@@ -0,0 +1,33 @@
+module Emails
+ module Issues
+ 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))
+ end
+
+ def reassigned_issue_email(recipient_id, issue_id, previous_assignee_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))
+ 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))
+ end
+
+ def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
+ @issue = Issue.find issue_id
+ @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))
+ end
+ end
+end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
new file mode 100644
index 00000000000..57c1925fa47
--- /dev/null
+++ b/app/mailers/emails/merge_requests.rb
@@ -0,0 +1,66 @@
+module Emails
+ module MergeRequests
+ def new_merge_request_email(recipient_id, merge_request_id)
+ @merge_request = MergeRequest.find(merge_request_id)
+ mail(to: recipient(recipient_id), subject: subject("new merge request !#{@merge_request.iid}", @merge_request.title))
+ end
+
+ def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
+ @merge_request = MergeRequest.find(merge_request_id)
+ @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id
+ mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.iid}", @merge_request.title))
+ 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
+ mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.iid}", @merge_request.title))
+ end
+
+ def merged_merge_request_email(recipient_id, merge_request_id)
+ @merge_request = MergeRequest.find(merge_request_id)
+ mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.iid}", @merge_request.title))
+ end
+ end
+
+ # Over rides default behavour to show source/target
+ # Formats arguments into a String suitable for use as an email subject
+ #
+ # extra - Extra Strings to be inserted into the subject
+ #
+ # Examples
+ #
+ # >> subject('Lorem ipsum')
+ # => "GitLab Merge Request | Lorem ipsum"
+ #
+ # # Automatically inserts Project name:
+ # Forked MR
+ # => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
+ # => target project => <Project id: 2, name: "My Ror", path: "ruby_on_rails", ...>
+ # => source branch => source
+ # => target branch => target
+ # >> subject('Lorem ipsum')
+ # => "GitLab Merge Request | Ruby on Rails:source >> My Ror:target | Lorem ipsum "
+ #
+ # Non Forked MR
+ # => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
+ # => target project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
+ # => source branch => source
+ # => target branch => target
+ # >> subject('Lorem ipsum')
+ # => "GitLab Merge Request | Ruby on Rails | source >> target | Lorem ipsum "
+ # # Accepts multiple arguments
+ # >> subject('Lorem ipsum', 'Dolor sit amet')
+ # => "GitLab Merge Request | Lorem ipsum | Dolor sit amet"
+ def subject(*extra)
+ subject = "GitLab 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
+ subject << "#{@merge_request.source_project.name_with_namespace} | #{merge_request.source_branch} >> #{merge_request.target_branch}"
+ end
+ subject << " | " + extra.join(' | ') if extra.present?
+ subject
+ end
+
+end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
new file mode 100644
index 00000000000..761b4c8161f
--- /dev/null
+++ b/app/mailers/emails/notes.rb
@@ -0,0 +1,30 @@
+module Emails
+ module Notes
+ def note_commit_email(recipient_id, note_id)
+ @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))
+ 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}"))
+ 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}"))
+ 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"))
+ end
+ end
+end
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
new file mode 100644
index 00000000000..bcd44f9476c
--- /dev/null
+++ b/app/mailers/emails/profile.rb
@@ -0,0 +1,15 @@
+module Emails
+ module Profile
+ def new_user_email(user_id, password)
+ @user = User.find(user_id)
+ @password = password
+ mail(to: @user.email, subject: subject("Account was created for you"))
+ end
+
+ def new_ssh_key_email(key_id)
+ @key = Key.find(key_id)
+ @user = @key.user
+ mail(to: @user.email, subject: subject("SSH key was added to your account"))
+ end
+ end
+end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
new file mode 100644
index 00000000000..4d5fe9ef614
--- /dev/null
+++ b/app/mailers/emails/projects.rb
@@ -0,0 +1,17 @@
+module Emails
+ module Projects
+ def project_access_granted_email(user_project_id)
+ @users_project = UsersProject.find user_project_id
+ @project = @users_project.project
+ mail(to: @users_project.user.email,
+ subject: subject("access to project was granted"))
+ end
+
+ def project_was_moved_email(project_id, user_id)
+ @user = User.find user_id
+ @project = Project.find project_id
+ mail(to: @user.email,
+ subject: subject("project was moved"))
+ end
+ end
+end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 08f7e01aab1..2f7be00c33e 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -1,7 +1,14 @@
class Notify < ActionMailer::Base
+ include Emails::Issues
+ include Emails::MergeRequests
+ include Emails::Notes
+ include Emails::Projects
+ include Emails::Profile
+ include Emails::Groups
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
+ add_template_helper MergeRequestsHelper
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
@@ -15,117 +22,6 @@ class Notify < ActionMailer::Base
delay_for(2.seconds)
end
-
- #
- # Issue
- #
-
- def new_issue_email(issue_id)
- @issue = Issue.find(issue_id)
- @project = @issue.project
- mail(to: @issue.assignee_email, subject: subject("new issue ##{@issue.id}", @issue.title))
- end
-
- def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
- @issue = Issue.find(issue_id)
- @previous_assignee ||= User.find(previous_assignee_id)
- @project = @issue.project
- mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.id}", @issue.title))
- end
-
- def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
- @issue = Issue.find issue_id
- @issue_status = status
- @project = @issue.project
- @updated_by = User.find updated_by_user_id
- mail(to: recipient(recipient_id),
- subject: subject("changed issue ##{@issue.id}", @issue.title))
- end
-
-
-
- #
- # Merge Request
- #
-
- def new_merge_request_email(merge_request_id)
- @merge_request = MergeRequest.find(merge_request_id)
- @project = @merge_request.project
- mail(to: @merge_request.assignee_email, subject: subject("new merge request !#{@merge_request.id}", @merge_request.title))
- end
-
- def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
- @merge_request = MergeRequest.find(merge_request_id)
- @previous_assignee ||= User.find(previous_assignee_id)
- @project = @merge_request.project
- mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title))
- end
-
-
-
- #
- # Note
- #
-
- def note_commit_email(recipient_id, note_id)
- @note = Note.find(note_id)
- @commit = @note.noteable
- @commit = CommitDecorator.decorate(@commit)
- @project = @note.project
- mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title))
- 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.id}"))
- 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.id}"))
- 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"))
- end
-
-
- #
- # Project
- #
-
- def project_access_granted_email(user_project_id)
- @users_project = UsersProject.find user_project_id
- @project = @users_project.project
- mail(to: @users_project.user.email,
- subject: subject("access to project was granted"))
- end
-
-
- def project_was_moved_email(user_project_id)
- @users_project = UsersProject.find user_project_id
- @project = @users_project.project
- mail(to: @users_project.user.email,
- subject: subject("project was moved"))
- end
-
- #
- # User
- #
-
- def new_user_email(user_id, password)
- @user = User.find(user_id)
- @password = password
- mail(to: @user.email, subject: subject("Account was created for you"))
- end
-
-
private
# Look up a User by their ID and return their email address
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 6d087a959a9..85476089145 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -1,24 +1,54 @@
class Ability
class << self
def allowed(user, subject)
+ return not_auth_abilities(user, subject) if user.nil?
return [] unless user.kind_of?(User)
+ return [] if user.blocked?
case subject.class.name
when "Project" then project_abilities(user, subject)
when "Issue" then issue_abilities(user, subject)
when "Note" then note_abilities(user, subject)
- when "Snippet" then snippet_abilities(user, subject)
+ when "ProjectSnippet" then project_snippet_abilities(user, subject)
+ when "PersonalSnippet" then personal_snippet_abilities(user, subject)
when "MergeRequest" then merge_request_abilities(user, subject)
- when "Group", "Namespace" then group_abilities(user, subject)
- when "UserTeam" then user_team_abilities(user, subject)
+ when "Group" then group_abilities(user, subject)
+ when "Namespace" then namespace_abilities(user, subject)
else []
end.concat(global_abilities(user))
end
+ # List of possible abilities
+ # for non-authenticated user
+ def not_auth_abilities(user, subject)
+ project = if subject.kind_of?(Project)
+ subject
+ elsif subject.respond_to?(:project)
+ subject.project
+ else
+ nil
+ end
+
+ if project && project.public
+ [
+ :read_project,
+ :read_wiki,
+ :read_issue,
+ :read_milestone,
+ :read_project_snippet,
+ :read_team_member,
+ :read_merge_request,
+ :read_note,
+ :download_code
+ ]
+ else
+ []
+ end
+ end
+
def global_abilities(user)
rules = []
rules << :create_group if user.can_create_group
- rules << :create_team if user.can_create_team
rules
end
@@ -41,20 +71,35 @@ class Ability
rules << project_guest_rules
end
- if project.owner == user
+ if project.public?
+ rules << public_project_rules
+ end
+
+ if project.owner == user || user.admin?
+ rules << project_admin_rules
+ end
+
+ if project.group && project.group.has_owner?(user)
rules << project_admin_rules
end
rules.flatten
end
+ def public_project_rules
+ project_guest_rules + [
+ :download_code,
+ :fork_project,
+ ]
+ end
+
def project_guest_rules
[
:read_project,
:read_wiki,
:read_issue,
:read_milestone,
- :read_snippet,
+ :read_project_snippet,
:read_team_member,
:read_merge_request,
:read_note,
@@ -67,7 +112,8 @@ class Ability
def project_report_rules
project_guest_rules + [
:download_code,
- :write_snippet
+ :fork_project,
+ :write_project_snippet
]
end
@@ -83,15 +129,14 @@ class Ability
project_dev_rules + [
:push_code_to_protected_branches,
:modify_issue,
- :modify_snippet,
+ :modify_project_snippet,
:modify_merge_request,
:admin_issue,
:admin_milestone,
- :admin_snippet,
+ :admin_project_snippet,
:admin_team_member,
:admin_merge_request,
:admin_note,
- :accept_mr,
:admin_wiki,
:admin_project
]
@@ -109,8 +154,12 @@ class Ability
def group_abilities user, group
rules = []
+ if group.users.include?(user) || user.admin?
+ rules << :read_group
+ end
+
# Only group owner and administrators can manage group
- if group.owner == user || user.admin?
+ if group.has_owner?(user) || user.admin?
rules << [
:manage_group,
:manage_namespace
@@ -120,23 +169,20 @@ class Ability
rules.flatten
end
- def user_team_abilities user, team
+ def namespace_abilities user, namespace
rules = []
- # Only group owner and administrators can manage group
- if team.owner == user || team.admin?(user) || user.admin?
- rules << [ :manage_user_team ]
- end
-
- if team.owner == user || user.admin?
- rules << [ :admin_user_team ]
+ # Only namespace owner and administrators can manage it
+ if namespace.owner == user || user.admin?
+ rules << [
+ :manage_namespace
+ ]
end
rules.flatten
end
-
- [:issue, :note, :snippet, :merge_request].each do |name|
+ [:issue, :note, :project_snippet, :personal_snippet, :merge_request].each do |name|
define_method "#{name}_abilities" do |user, subject|
if subject.author == user
[
diff --git a/app/models/campfire_service.rb b/app/models/campfire_service.rb
new file mode 100644
index 00000000000..fb2a49fd586
--- /dev/null
+++ b/app/models/campfire_service.rb
@@ -0,0 +1,78 @@
+# == 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)
+#
+
+class CampfireService < Service
+ attr_accessible :subdomain, :room
+
+ validates :token, presence: true, if: :activated?
+
+ def title
+ 'Campfire'
+ end
+
+ def description
+ 'Simple web-based real-time group chat'
+ end
+
+ def to_param
+ 'campfire'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'token', placeholder: '' },
+ { type: 'text', name: 'subdomain', placeholder: '' },
+ { type: 'text', name: 'room', placeholder: '' }
+ ]
+ end
+
+ def execute(push_data)
+ room = gate.find_room_by_name(self.room)
+ return true unless room
+
+ message = build_message(push_data)
+
+ room.speak(message)
+ end
+
+ private
+
+ def gate
+ @gate ||= Tinder::Campfire.new(subdomain, token: token)
+ end
+
+ def build_message(push)
+ ref = push[:ref].gsub("refs/heads/", "")
+ before = push[:before]
+ after = push[:after]
+
+ message = ""
+ message << "[#{project.name_with_namespace}] "
+ message << "#{push[:user_name]} "
+
+ if before =~ /000000/
+ message << "pushed new branch #{ref} \n"
+ elsif after =~ /000000/
+ message << "removed branch #{ref} \n"
+ else
+ message << "pushed #{push[:total_commits_count]} commits to #{ref}. "
+ message << "#{project.web_url}/compare/#{before}...#{after}"
+ end
+
+ message
+ end
+end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 17d41f27f34..dd1f9801878 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -2,167 +2,141 @@ class Commit
include ActiveModel::Conversion
include StaticModel
extend ActiveModel::Naming
+ include Mentionable
- # Safe amount of files with diffs in one commit to render
+ attr_mentionable :safe_message
+
+ # Safe amount of changes (files and lines) in one commit to render
# Used to prevent 500 error on huge commits by suppressing diff
#
- DIFF_SAFE_SIZE = 100
-
- attr_accessor :commit, :head, :refs
-
- delegate :message, :authored_date, :committed_date, :parents, :sha,
- :date, :committer, :author, :diffs, :tree, :id, :stats,
- :to_patch, to: :commit
-
- class << self
- def find_or_first(repo, commit_id = nil, root_ref)
- commit = if commit_id
- repo.commit(commit_id)
- else
- repo.commits(root_ref).first
- end
-
- Commit.new(commit) if commit
- end
-
- def fresh_commits(repo, n = 10)
- commits = repo.heads.map do |h|
- repo.commits(h.name, n).map { |c| Commit.new(c, h) }
- end.flatten.uniq { |c| c.id }
-
- commits.sort! do |x, y|
- y.committed_date <=> x.committed_date
- end
-
- commits[0...n]
- end
-
- def commits_with_refs(repo, n = 20)
- commits = repo.branches.map { |ref| Commit.new(ref.commit, ref) }
-
- commits.sort! do |x, y|
- y.committed_date <=> x.committed_date
- end
-
- commits[0..n]
- end
-
- def commits_since(repo, date)
- commits = repo.heads.map do |h|
- repo.log(h.name, nil, since: date).each { |c| Commit.new(c, h) }
- end.flatten.uniq { |c| c.id }
+ # User can force display of diff above this size
+ DIFF_SAFE_FILES = 100
+ DIFF_SAFE_LINES = 5000
+ # Commits above this size will not be rendered in HTML
+ DIFF_HARD_LIMIT_FILES = 500
+ DIFF_HARD_LIMIT_LINES = 10000
+
+ def self.decorate(commits)
+ commits.map { |c| self.new(c) }
+ end
- commits.sort! do |x, y|
- y.committed_date <=> x.committed_date
- end
+ # Calculate number of lines to render for diffs
+ def self.diff_line_count(diffs)
+ diffs.reduce(0){|sum, d| sum + d.diff.lines.count}
+ end
- commits
- end
+ def self.diff_suppress?(diffs, line_count = nil)
+ # optimize - check file count first
+ return true if diffs.size > DIFF_SAFE_FILES
- def commits(repo, ref, path = nil, limit = nil, offset = nil)
- if path
- repo.log(ref, path, max_count: limit, skip: offset)
- elsif limit && offset
- repo.commits(ref, limit, offset)
- else
- repo.commits(ref)
- end.map{ |c| Commit.new(c) }
- end
-
- def commits_between(repo, from, to)
- repo.commits_between(from, to).map { |c| Commit.new(c) }
- end
+ line_count ||= Commit::diff_line_count(diffs)
+ line_count > DIFF_SAFE_LINES
+ end
- def compare(project, from, to)
- result = {
- commits: [],
- diffs: [],
- commit: nil,
- same: false
- }
+ def self.diff_force_suppress?(diffs, line_count = nil)
+ # optimize - check file count first
+ return true if diffs.size > DIFF_HARD_LIMIT_FILES
- return result unless from && to
+ line_count ||= Commit::diff_line_count(diffs)
+ line_count > DIFF_HARD_LIMIT_LINES
+ end
- first = project.repository.commit(to.try(:strip))
- last = project.repository.commit(from.try(:strip))
+ attr_accessor :raw
- if first && last
- result[:same] = (first.id == last.id)
- result[:commits] = project.repo.commits_between(last.id, first.id).map {|c| Commit.new(c)}
- result[:diffs] = project.repo.diff(last.id, first.id) rescue []
- result[:commit] = Commit.new(first)
- end
+ def initialize(raw_commit)
+ raise "Nil as raw commit passed" unless raw_commit
- result
- end
+ @raw = raw_commit
end
- def initialize(raw_commit, head = nil)
- raise "Nil as raw commit passed" unless raw_commit
-
- @commit = raw_commit
- @head = head
+ def id
+ @raw.id
end
- def short_id(length = 10)
- id.to_s[0..length]
+ def diff_line_count
+ @diff_line_count ||= Commit::diff_line_count(self.diffs)
+ @diff_line_count
end
- def safe_message
- @safe_message ||= message
+ def diff_suppress?
+ Commit::diff_suppress?(self.diffs, diff_line_count)
end
- def created_at
- committed_date
+ def diff_force_suppress?
+ Commit::diff_force_suppress?(self.diffs, diff_line_count)
end
- def author_email
- author.email
+ # Returns a string describing the commit for use in a link title
+ #
+ # Example
+ #
+ # "Commit: Alex Denisov - Project git clone panel"
+ def link_title
+ "Commit: #{author_name} - #{title}"
end
- def author_name
- author.name
+ # Returns the commits title.
+ #
+ # Usually, the commit title is the first line of the commit message.
+ # In case this first line is longer than 100 characters, it is cut off
+ # after 80 characters and ellipses (`&hellp;`) are appended.
+ def title
+ title = safe_message
+
+ return no_commit_message if title.blank?
+
+ title_end = title.index(/\n/)
+ if (!title_end && title.length > 100) || (title_end && title_end > 100)
+ title[0..79] << "&hellip;".html_safe
+ else
+ title.split(/\n/, 2).first
+ end
end
- # Was this commit committed by a different person than the original author?
- def different_committer?
- author_name != committer_name || author_email != committer_email
+ # Returns the commits description
+ #
+ # cut off, ellipses (`&hellp;`) are prepended to the commit message.
+ def description
+ description = safe_message
+
+ 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
end
- def committer_name
- committer.name
+ # Regular expression that identifies commit message clauses that trigger issue closing.
+ def issue_closing_regex
+ @issue_closing_regex ||= Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
end
- def committer_email
- committer.email
+ # Discover issues should be closed when this commit is pushed to a project's
+ # default branch.
+ def closes_issues project
+ md = issue_closing_regex.match(safe_message)
+ if md
+ extractor = Gitlab::ReferenceExtractor.new
+ extractor.analyze(md[0])
+ extractor.issues_for(project)
+ else
+ []
+ end
end
- def prev_commit
- @prev_commit ||= if parents.present?
- Commit.new(parents.first)
- else
- nil
- end
+ # Mentionable override.
+ def gfm_reference
+ "commit #{sha[0..5]}"
end
- def prev_commit_id
- prev_commit.try :id
+ def method_missing(m, *args, &block)
+ @raw.send(m, *args, &block)
end
- # Shows the diff between the commit's parent and the commit.
- #
- # Cuts out the header and stats from #to_patch and returns only the diff.
- def to_diff
- # see Grit::Commit#show
- patch = to_patch
-
- # discard lines before the diff
- lines = patch.split("\n")
- while !lines.first.start_with?("diff --git") do
- lines.shift
- end
- lines.pop if lines.last =~ /^[\d.]+$/ # Git version
- lines.pop if lines.last == "-- " # end of diff
- lines.join("\n")
+ def respond_to?(method)
+ return true if @raw.respond_to?(method)
+
+ super
end
end
diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb
new file mode 100644
index 00000000000..821ed54fb98
--- /dev/null
+++ b/app/models/concerns/internal_id.rb
@@ -0,0 +1,17 @@
+module InternalId
+ extend ActiveSupport::Concern
+
+ included do
+ validate :set_iid, on: :create
+ validates :iid, presence: true, numericality: true
+ end
+
+ def set_iid
+ max_iid = project.send(self.class.name.tableize).maximum(:iid)
+ self.iid = max_iid.to_i + 1
+ end
+
+ def to_param
+ iid.to_s
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 645b35ec660..7f820f950b0 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -6,25 +6,24 @@
#
module Issuable
extend ActiveSupport::Concern
+ include Mentionable
included do
- belongs_to :project
belongs_to :author, class_name: "User"
belongs_to :assignee, class_name: "User"
belongs_to :milestone
has_many :notes, as: :noteable, dependent: :destroy
- validates :project, presence: true
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
- validates :closed, inclusion: { in: [true, false] }
- scope :opened, -> { where(closed: false) }
- scope :closed, -> { where(closed: true) }
- 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 :assigned, ->(u) { where(assignee_id: u.id)}
+ scope :authored, ->(user) { where(author_id: user) }
+ scope :assigned_to, ->(u) { where(assignee_id: u.id)}
scope :recent, -> { order("created_at DESC") }
+ scope :assigned, -> { where("assignee_id IS NOT NULL") }
+ scope :unassigned, -> { where("assignee_id IS NULL") }
+ scope :of_projects, ->(ids) { where(project_id: ids) }
+
delegate :name,
:email,
@@ -38,6 +37,8 @@ module Issuable
prefix: true
attr_accessor :author_id_of_changes
+
+ attr_mentionable :title, :description
end
module ClassMethods
@@ -62,14 +63,6 @@ module Issuable
assignee_id_changed?
end
- def is_being_closed?
- closed_changed? && closed
- end
-
- def is_being_reopened?
- closed_changed? && !closed
- end
-
#
# Votes
#
@@ -104,4 +97,18 @@ module Issuable
def votes_count
upvotes + downvotes
end
+
+ # Return all users participating on the discussion
+ def participants
+ users = []
+ users << author
+ users << assignee if is_assigned?
+ mentions = []
+ mentions << self.mentioned_users
+ notes.each do |note|
+ users << note.author
+ mentions << note.mentioned_users
+ end
+ users.concat(mentions.reduce([], :|)).uniq
+ end
end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
new file mode 100644
index 00000000000..5858fe1bb6f
--- /dev/null
+++ b/app/models/concerns/mentionable.rb
@@ -0,0 +1,97 @@
+# == Mentionable concern
+#
+# Contains functionality related to objects that can mention Users, Issues, MergeRequests, or Commits by
+# GFM references.
+#
+# Used by Issue, Note, MergeRequest, and Commit.
+#
+module Mentionable
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Indicate which attributes of the Mentionable to search for GFM references.
+ def attr_mentionable *attrs
+ mentionable_attrs.concat(attrs.map(&:to_s))
+ end
+
+ # Accessor for attributes marked mentionable.
+ def mentionable_attrs
+ @mentionable_attrs ||= []
+ end
+ end
+
+ # Generate a GFM back-reference that will construct a link back to this Mentionable when rendered. Must
+ # be overridden if this model object can be referenced directly by GFM notation.
+ def gfm_reference
+ raise NotImplementedError.new("#{self.class} does not implement #gfm_reference")
+ end
+
+ # Construct a String that contains possible GFM references.
+ def mentionable_text
+ self.class.mentionable_attrs.map { |attr| send(attr) || '' }.join
+ end
+
+ # The GFM reference to this Mentionable, which shouldn't be included in its #references.
+ def local_reference
+ self
+ end
+
+ # Determine whether or not a cross-reference Note has already been created between this Mentionable and
+ # the specified target.
+ def has_mentioned? target
+ Note.cross_reference_exists?(target, local_reference)
+ end
+
+ def mentioned_users
+ users = []
+ return users if mentionable_text.blank?
+ has_project = self.respond_to? :project
+ matches = mentionable_text.scan(/@[a-zA-Z][a-zA-Z0-9_\-\.]*/)
+ matches.each do |match|
+ identifier = match.delete "@"
+ if has_project
+ id = project.team.members.find { |u| u.username == identifier }.try(:id)
+ else
+ id = User.where(username: identifier).pluck(:id).first
+ end
+ users << User.find(id) unless id.blank?
+ end
+ users.uniq
+ end
+
+ # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
+ def references p = project, text = mentionable_text
+ return [] if text.blank?
+ ext = Gitlab::ReferenceExtractor.new
+ ext.analyze(text)
+ (ext.issues_for(p) + ext.merge_requests_for(p) + ext.commits_for(p)).uniq - [local_reference]
+ end
+
+ # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
+ def create_cross_references! p = project, a = author, without = []
+ refs = references(p) - without
+ refs.each do |ref|
+ Note.create_cross_reference_note(ref, local_reference, a, p)
+ end
+ end
+
+ # If the mentionable_text field is about to change, locate any *added* references and create cross references for
+ # them. Invoke from an observer's #before_save implementation.
+ def notice_added_references p = project, a = author
+ ch = changed_attributes
+ original, mentionable_changed = "", false
+ self.class.mentionable_attrs.each do |attr|
+ if ch[attr]
+ original << ch[attr]
+ mentionable_changed = true
+ end
+ end
+
+ # Only proceed if the saved changes actually include a chance to an attr_mentionable field.
+ return unless mentionable_changed
+
+ preexisting = references(p, original)
+ create_cross_references!(p, a, preexisting)
+ end
+
+end
diff --git a/app/models/concerns/notifiable.rb b/app/models/concerns/notifiable.rb
new file mode 100644
index 00000000000..722f375e71d
--- /dev/null
+++ b/app/models/concerns/notifiable.rb
@@ -0,0 +1,15 @@
+# == Notifiable concern
+#
+# Contains notification functionality shared between UsersProject and UsersGroup
+#
+module Notifiable
+ extend ActiveSupport::Concern
+
+ included do
+ validates :notification_level, inclusion: { in: Notification.project_notification_levels }, presence: true
+ end
+
+ def notification
+ @notification ||= Notification.new(self)
+ end
+end
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
new file mode 100644
index 00000000000..47aeb93a419
--- /dev/null
+++ b/app/models/deploy_key.rb
@@ -0,0 +1,20 @@
+# == Schema Information
+#
+# Table name: keys
+#
+# id :integer not null, primary key
+# user_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# key :text
+# title :string(255)
+# type :string(255)
+# fingerprint :string(255)
+#
+
+class DeployKey < Key
+ has_many :deploy_keys_projects, dependent: :destroy
+ has_many :projects, through: :deploy_keys_projects
+
+ scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
+end
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
new file mode 100644
index 00000000000..6f109e48314
--- /dev/null
+++ b/app/models/deploy_keys_project.rb
@@ -0,0 +1,22 @@
+# == Schema Information
+#
+# Table name: deploy_keys_projects
+#
+# id :integer not null, primary key
+# deploy_key_id :integer not null
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class DeployKeysProject < ActiveRecord::Base
+ attr_accessible :key_id, :project_id
+
+ belongs_to :project
+ belongs_to :deploy_key
+
+ validates :deploy_key_id, presence: true
+ validates :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
+
+ validates :project_id, presence: true
+end
diff --git a/app/models/event.rb b/app/models/event.rb
index 18422e192a4..095a06c956b 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -54,6 +54,27 @@ class Event < ActiveRecord::Base
Event::COMMENTED
end
end
+
+ def create_ref_event(project, user, ref, action = 'add', prefix = 'refs/heads')
+ if action.to_s == 'add'
+ before = '00000000'
+ after = ref.commit.id
+ else
+ before = ref.commit.id
+ after = '00000000'
+ end
+
+ Event.create(
+ project: project,
+ action: Event::PUSHED,
+ data: {
+ ref: "#{prefix}/#{ref.name}",
+ before: before,
+ after: after
+ },
+ author_id: user.id
+ )
+ end
end
def proper?
@@ -68,14 +89,16 @@ class Event < ActiveRecord::Base
def project_name
if project
- project.name
+ project.name_with_namespace
else
"(deleted project)"
end
end
def target_title
- target.try :title
+ if target && target.respond_to?(:title)
+ target.title
+ end
end
def push?
@@ -130,15 +153,11 @@ class Event < ActiveRecord::Base
target if target_type == "MergeRequest"
end
- def author
- @author ||= User.find(author_id)
- end
-
def action_name
if closed?
"closed"
elsif merged?
- "merged"
+ "accepted"
elsif joined?
'joined'
elsif left?
@@ -204,7 +223,7 @@ class Event < ActiveRecord::Base
# Max 20 commits from push DESC
def commits
- @commits ||= data[:commits].map { |commit| repository.commit(commit[:id]) }.reverse
+ @commits ||= data[:commits].reverse
end
def commits_count
@@ -225,26 +244,8 @@ class Event < ActiveRecord::Base
end
end
- def repository
- project.repository
- end
-
- def parent_commit
- repository.commit(commit_from)
- rescue => ex
- nil
- end
-
- def last_commit
- repository.commit(commit_to)
- rescue => ex
- nil
- end
-
def push_with_commits?
- md_ref? && commits.any? && parent_commit && last_commit
- rescue Grit::NoSuchPathError
- false
+ md_ref? && commits.any? && commit_from && commit_to
end
def last_push_to_non_root?
@@ -255,6 +256,10 @@ class Event < ActiveRecord::Base
target.commit_id
end
+ def target_iid
+ target.respond_to?(:iid) ? target.iid : target_id
+ end
+
def note_short_commit_id
note_commit_id[0..8]
end
@@ -263,6 +268,10 @@ class Event < ActiveRecord::Base
target.noteable_type == "Commit"
end
+ def note_project_snippet?
+ target.noteable_type == "Snippet"
+ end
+
def note_target
target.noteable
end
@@ -275,6 +284,14 @@ class Event < ActiveRecord::Base
end
end
+ def note_target_iid
+ if note_target.respond_to?(:iid)
+ note_target.iid
+ else
+ note_target_id
+ end.to_s
+ end
+
def wall_note?
target.noteable_type.blank?
end
@@ -286,4 +303,14 @@ class Event < ActiveRecord::Base
"Wall"
end.downcase
end
+
+ def body?
+ if push?
+ push_with_commits?
+ elsif note?
+ true
+ else
+ target.respond_to? :title
+ end
+ end
end
diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb
new file mode 100644
index 00000000000..aaa527a1145
--- /dev/null
+++ b/app/models/forked_project_link.rb
@@ -0,0 +1,19 @@
+# == Schema Information
+#
+# Table name: forked_project_links
+#
+# id :integer not null, primary key
+# forked_to_project_id :integer not null
+# forked_from_project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class ForkedProjectLink < ActiveRecord::Base
+ attr_accessible :forked_from_project_id, :forked_to_project_id
+
+ # Relations
+ belongs_to :forked_to_project, class_name: Project
+ belongs_to :forked_from_project, class_name: Project
+
+end
diff --git a/app/models/gitlab_ci_service.rb b/app/models/gitlab_ci_service.rb
index 4eb39c7ef4d..7f5380a4551 100644
--- a/app/models/gitlab_ci_service.rb
+++ b/app/models/gitlab_ci_service.rb
@@ -11,6 +11,8 @@
# updated_at :datetime not null
# active :boolean default(FALSE), not null
# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
#
class GitlabCiService < Service
@@ -46,4 +48,31 @@ class GitlabCiService < Service
def build_page sha
project_url + "/builds/#{sha}"
end
+
+ def builds_path
+ project_url + "?ref=" + project.default_branch
+ end
+
+ def status_img_path
+ project_url + "/status.png?ref=" + project.default_branch
+ end
+
+ def title
+ 'GitLab CI'
+ end
+
+ def description
+ 'Continuous integration server from GitLab'
+ end
+
+ def to_param
+ 'gitlab_ci'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' },
+ { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3'}
+ ]
+ end
end
diff --git a/app/models/gollum_wiki.rb b/app/models/gollum_wiki.rb
new file mode 100644
index 00000000000..d1edbab4533
--- /dev/null
+++ b/app/models/gollum_wiki.rb
@@ -0,0 +1,119 @@
+class GollumWiki
+
+ MARKUPS = {
+ "Markdown" => :markdown,
+ "RDoc" => :rdoc
+ }
+
+ class CouldNotCreateWikiError < StandardError; end
+
+ # Returns a string describing what went wrong after
+ # an operation fails.
+ attr_reader :error_message
+
+ def initialize(project, user = nil)
+ @project = project
+ @user = user
+ end
+
+ def path
+ @project.path + '.wiki'
+ end
+
+ def path_with_namespace
+ @project.path_with_namespace + ".wiki"
+ end
+
+ def url_to_repo
+ gitlab_shell.url_to_repo(path_with_namespace)
+ end
+
+ def ssh_url_to_repo
+ url_to_repo
+ end
+
+ def http_url_to_repo
+ http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
+ end
+
+ # Returns the Gollum::Wiki object.
+ def wiki
+ @wiki ||= begin
+ Gollum::Wiki.new(path_to_repo)
+ rescue Grit::NoSuchPathError
+ create_repo!
+ end
+ end
+
+ # Returns an Array of Gitlab WikiPage instances or an
+ # empty Array if this Wiki has no pages.
+ def pages
+ wiki.pages.map { |page| WikiPage.new(self, page, true) }
+ end
+
+ # Finds a page within the repository based on a tile
+ # or slug.
+ #
+ # title - The human readable or parameterized title of
+ # the page.
+ #
+ # Returns an initialized WikiPage instance or nil
+ def find_page(title, version = nil)
+ if page = wiki.page(title, version)
+ WikiPage.new(self, page, true)
+ else
+ nil
+ end
+ end
+
+ def create_page(title, content, format = :markdown, message = nil)
+ commit = commit_details(:created, message, title)
+
+ wiki.write_page(title, format, content, commit)
+ rescue Gollum::DuplicatePageError => e
+ @error_message = "Duplicate page: #{e.message}"
+ return false
+ end
+
+ def update_page(page, content, format = :markdown, message = nil)
+ commit = commit_details(:updated, message, page.title)
+
+ wiki.update_page(page, page.name, format, content, commit)
+ end
+
+ def delete_page(page, message = nil)
+ wiki.delete_page(page, commit_details(:deleted, message, page.title))
+ end
+
+ private
+
+ def create_repo!
+ if init_repo(path_with_namespace)
+ Gollum::Wiki.new(path_to_repo)
+ else
+ raise CouldNotCreateWikiError
+ end
+ end
+
+ def init_repo(path_with_namespace)
+ gitlab_shell.add_repository(path_with_namespace)
+ end
+
+ def commit_details(action, message = nil, title = nil)
+ commit_message = message || default_message(action, title)
+
+ {email: @user.email, name: @user.name, message: commit_message}
+ end
+
+ def default_message(action, title)
+ "#{@user.username} #{action} page: #{title}"
+ end
+
+ def gitlab_shell
+ @gitlab_shell ||= Gitlab::Shell.new
+ end
+
+ def path_to_repo
+ @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
+ end
+end
diff --git a/app/models/group.rb b/app/models/group.rb
index 8ba92980a9b..0cc3a3a0f41 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -2,35 +2,51 @@
#
# Table name: namespaces
#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# type :string(255)
+# id :integer not null, primary key
+# name :string(255) not null
+# path :string(255) not null
+# owner_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# type :string(255)
+# description :string(255) default(""), not null
#
class Group < Namespace
- def add_users_to_project_teams(user_ids, project_access)
- UsersProject.add_users_into_projects(
- projects.map(&:id),
- user_ids,
- project_access
- )
+ has_many :users_groups, dependent: :destroy
+ has_many :users, through: :users_groups
+
+ def human_name
+ name
end
- def users
- users = User.joins(:users_projects).where(users_projects: {project_id: project_ids})
- users = users << owner
- users.uniq
+ def owners
+ @owners ||= users_groups.owners.map(&:user)
end
- def human_name
- name
+ def add_users(user_ids, group_access)
+ user_ids.compact.each do |user_id|
+ self.users_groups.create(user_id: user_id, group_access: group_access)
+ end
+ end
+
+ def add_user(user, group_access)
+ self.users_groups.create(user_id: user.id, group_access: group_access)
+ end
+
+ def add_owner(user)
+ self.add_user(user, UsersGroup::OWNER)
+ end
+
+ def has_owner?(user)
+ owners.include?(user)
+ end
+
+ def last_owner?(user)
+ has_owner?(user) && owners.size == 1
end
- def truncate_teams
- UsersProject.truncate_teams(project_ids)
+ def members
+ users_groups
end
end
diff --git a/app/models/hipchat_service.rb b/app/models/hipchat_service.rb
new file mode 100644
index 00000000000..c3fb4826334
--- /dev/null
+++ b/app/models/hipchat_service.rb
@@ -0,0 +1,75 @@
+# == 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)
+#
+
+class HipchatService < Service
+ attr_accessible :room
+
+ validates :token, presence: true, if: :activated?
+
+ def title
+ 'Hipchat'
+ end
+
+ def description
+ 'Simple web-based real-time group chat'
+ end
+
+ def to_param
+ 'hipchat'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'token', placeholder: '' },
+ { type: 'text', name: 'room', placeholder: '' }
+ ]
+ end
+
+ def execute(push_data)
+ gate[room].send('Gitlab', create_message(push_data))
+ end
+
+ private
+
+ def gate
+ @gate ||= HipChat::Client.new(token)
+ end
+
+ def create_message(push)
+ ref = push[:ref].gsub("refs/heads/", "")
+ before = push[:before]
+ after = push[:after]
+
+ message = ""
+ message << "#{push[:user_name]} "
+ if before =~ /000000/
+ message << "pushed new branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> to <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a>\n"
+ elsif after =~ /000000/
+ message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n"
+ else
+ message << "#pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> "
+ message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> "
+ message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
+ for commit in push[:commits] do
+ message << "<br /> - #{commit[:message]} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
+ end
+ end
+
+ message
+ end
+
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 07c0401143c..f3ec322126f 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -9,22 +9,57 @@
# project_id :integer
# created_at :datetime not null
# updated_at :datetime not null
-# closed :boolean default(FALSE), not null
# position :integer default(0)
# branch_name :string(255)
# description :text
# milestone_id :integer
+# state :string(255)
+# iid :integer
#
class Issue < ActiveRecord::Base
include Issuable
+ include InternalId
- attr_accessible :title, :assignee_id, :closed, :position, :description,
- :milestone_id, :label_list, :author_id_of_changes
+ belongs_to :project
+ validates :project, presence: true
+
+ 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) }
+ scope :closed, -> { with_state(:closed) }
+
+ attr_accessible :title, :assignee_id, :position, :description,
+ :milestone_id, :label_list, :author_id_of_changes,
+ :state_event
acts_as_taggable_on :labels
- def self.open_for(user)
- opened.assigned(user)
+ scope :cared, ->(user) { where(assignee_id: user) }
+ scope :open_for, ->(user) { opened.assigned_to(user) }
+
+ state_machine :state, initial: :opened do
+ event :close do
+ transition [:reopened, :opened] => :closed
+ end
+
+ event :reopen do
+ transition closed: :reopened
+ end
+
+ state :opened
+
+ state :reopened
+
+ state :closed
+ end
+
+ # Both open and reopened issues should be listed as opened
+ scope :opened, -> { with_state(:opened, :reopened) }
+
+ # Mentionable overrides.
+
+ def gfm_reference
+ "issue ##{iid}"
end
end
diff --git a/app/models/key.rb b/app/models/key.rb
index 64441ea54eb..79f7bbd2590 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -2,74 +2,64 @@
#
# Table name: keys
#
-# id :integer not null, primary key
-# user_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-# key :text
-# title :string(255)
-# identifier :string(255)
-# project_id :integer
+# id :integer not null, primary key
+# user_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# key :text
+# title :string(255)
+# type :string(255)
+# fingerprint :string(255)
#
require 'digest/md5'
class Key < ActiveRecord::Base
+ include Gitlab::Popen
+
belongs_to :user
- belongs_to :project
attr_accessible :key, :title
- before_validation :strip_white_space
- before_save :set_identifier
+ before_validation :strip_white_space, :generate_fingerpint
validates :title, presence: true, length: { within: 0..255 }
- validates :key, presence: true, length: { within: 0..5000 }, format: { :with => /ssh-.{3} / }, uniqueness: true
- validate :fingerprintable_key
+ validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
+ validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' }
delegate :name, :email, to: :user, prefix: true
def strip_white_space
- self.key = self.key.strip unless self.key.blank?
+ self.key = key.strip unless key.blank?
end
- def fingerprintable_key
- return true unless key # Don't test if there is no key.
-
- file = Tempfile.new('key_file')
- begin
- file.puts key
- file.rewind
- fingerprint_output = `ssh-keygen -lf #{file.path} 2>&1` # Catch stderr.
- ensure
- file.close
- file.unlink # deletes the temp file
- end
- errors.add(:key, "can't be fingerprinted") if fingerprint_output.match("failed")
+ # projects that has this key
+ def projects
+ user.authorized_projects
end
- def set_identifier
- if is_deploy_key
- self.identifier = "deploy_#{Digest::MD5.hexdigest(key)}"
- else
- self.identifier = "#{user.identifier}_#{Time.now.to_i}"
- end
+ def shell_id
+ "key-#{id}"
end
- def is_deploy_key
- !!project_id
- end
+ private
- # projects that has this key
- def projects
- if is_deploy_key
- [project]
- else
- user.authorized_projects
+ def generate_fingerpint
+ self.fingerprint = nil
+ return unless key.present?
+
+ cmd_status = 0
+ cmd_output = ''
+ Tempfile.open('gitlab_key_file') do |file|
+ file.puts key
+ file.rewind
+ cmd_output, cmd_status = popen("ssh-keygen -lf #{file.path}", '/tmp')
end
- end
- def shell_id
- "key-#{self.id}"
+ if cmd_status.zero?
+ cmd_output.gsub /([\d\h]{2}:)+[\d\h]{2}/ do |match|
+ self.fingerprint = match
+ end
+ end
end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 345b8d6e07d..7f367588b23 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -2,21 +2,22 @@
#
# Table name: merge_requests
#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# closed :boolean default(FALSE), not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# st_commits :text(2147483647)
-# st_diffs :text(2147483647)
-# merged :boolean default(FALSE), not null
-# state :integer default(1), not null
-# milestone_id :integer
+# id :integer not null, primary key
+# target_branch :string(255) not null
+# source_branch :string(255) not null
+# source_project_id :integer not null
+# author_id :integer
+# assignee_id :integer
+# title :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+# st_commits :text(2147483647)
+# st_diffs :text(2147483647)
+# milestone_id :integer
+# state :string(255)
+# merge_status :string(255)
+# target_project_id :integer not null
+# iid :integer
#
require Rails.root.join("app/models/commit")
@@ -24,45 +25,92 @@ require Rails.root.join("lib/static_model")
class MergeRequest < ActiveRecord::Base
include Issuable
+ include InternalId
- attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id,
- :author_id_of_changes
+ belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
+ belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
+
+ 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_accessor :should_remove_source_branch
- BROKEN_DIFF = "--broken-diff"
+ state_machine :state, initial: :opened do
+ event :close do
+ transition [:reopened, :opened] => :closed
+ end
+
+ event :merge do
+ transition [:reopened, :opened] => :merged
+ end
+
+ event :reopen do
+ transition closed: :reopened
+ end
+
+ state :opened
+
+ state :reopened
+
+ state :closed
+
+ state :merged
+ end
+
+ state_machine :merge_status, initial: :unchecked do
+ event :mark_as_unchecked do
+ transition [:can_be_merged, :cannot_be_merged] => :unchecked
+ end
+
+ event :mark_as_mergeable do
+ transition unchecked: :can_be_merged
+ end
+
+ event :mark_as_unmergeable do
+ transition unchecked: :cannot_be_merged
+ end
+
+ state :unchecked
- UNCHECKED = 1
- CAN_BE_MERGED = 2
- CANNOT_BE_MERGED = 3
+ state :can_be_merged
+
+ state :cannot_be_merged
+ end
serialize :st_commits
serialize :st_diffs
+ validates :source_project, presence: true
validates :source_branch, presence: true
+ validates :target_project, presence: true
validates :target_branch, presence: true
validate :validate_branches
- def self.find_all_by_branch(branch_name)
- where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name)
- end
+ 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) }
+ scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
+ scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
+ scope :of_projects, ->(ids) { where(target_project_id: ids) }
+ # Closed scope for merge request should return
+ # both merged and closed mr's
+ scope :closed, -> { with_states(:closed, :merged) }
- def self.find_all_by_milestone(milestone)
- where("milestone_id = :milestone_id", milestone_id: milestone)
- end
+ def validate_branches
+ if target_project==source_project && target_branch == source_branch
+ errors.add :branch_conflict, "You can not use same project/branch for source and target"
+ end
- def human_state
- states = {
- CAN_BE_MERGED => "can_be_merged",
- CANNOT_BE_MERGED => "cannot_be_merged",
- UNCHECKED => "unchecked"
- }
- states[self.state]
- end
+ if opened? || reopened?
+ similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened
+ similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
- def validate_branches
- if target_branch == source_branch
- errors.add :base, "You can not use same branch for source and target branches"
+ if similar_mrs.any?
+ errors.add :base, "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
+ end
end
end
@@ -71,45 +119,29 @@ class MergeRequest < ActiveRecord::Base
self.reloaded_diffs
end
- def unchecked?
- state == UNCHECKED
- end
-
- def mark_as_unchecked
- self.state = UNCHECKED
- self.save
- end
-
- def can_be_merged?
- state == CAN_BE_MERGED
- end
-
def check_if_can_be_merged
- self.state = if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
- CAN_BE_MERGED
- else
- CANNOT_BE_MERGED
- end
- self.save
+ if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
+ mark_as_mergeable
+ else
+ mark_as_unmergeable
+ end
end
def diffs
- st_diffs || []
+ @diffs ||= (load_diffs(st_diffs) || [])
end
def reloaded_diffs
- if open? && unmerged_diffs.any?
- self.st_diffs = unmerged_diffs
+ if opened? && unmerged_diffs.any?
+ self.st_diffs = dump_diffs(unmerged_diffs)
self.save
end
-
- rescue Grit::Git::GitTimeout
- self.st_diffs = [BROKEN_DIFF]
- self.save
end
def broken_diffs?
- diffs == [BROKEN_DIFF]
+ diffs == broken_diffs
+ rescue
+ true
end
def valid_diffs?
@@ -117,78 +149,64 @@ class MergeRequest < ActiveRecord::Base
end
def unmerged_diffs
- # Only show what is new in the source branch compared to the target branch, not the other way around.
- # The linex below with merge_base is equivalent to diff with three dots (git diff branch1...branch2)
- # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
- common_commit = project.repo.git.native(:merge_base, {}, [target_branch, source_branch]).strip
- diffs = project.repo.diff(common_commit, source_branch)
+ diffs = if for_fork?
+ Gitlab::Satellite::MergeAction.new(author, self).diffs_between_satellite
+ else
+ Gitlab::Git::Diff.between(target_project.repository, source_branch, target_branch)
+ end
+
+ diffs ||= []
+ diffs
end
def last_commit
commits.first
end
- def merged?
- merged && merge_event
- end
-
def merge_event
- self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
+ self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
end
def closed_event
- self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
+ self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end
def commits
- st_commits || []
+ load_commits(st_commits || [])
end
def probably_merged?
unmerged_commits.empty? &&
- commits.any? && open?
- end
-
- def open?
- !closed
- end
-
- def mark_as_merged!
- self.merged = true
- self.closed = true
- save
- end
-
- def mark_as_unmergable
- self.state = CANNOT_BE_MERGED
- self.save
+ commits.any? && opened?
end
def reloaded_commits
- if open? && unmerged_commits.any?
- self.st_commits = unmerged_commits
+ if opened? && unmerged_commits.any?
+ self.st_commits = dump_commits(unmerged_commits)
save
+
end
commits
end
def unmerged_commits
- self.project.repo.
- commits_between(self.target_branch, self.source_branch).
- map {|c| Commit.new(c)}.
+ if for_fork?
+ commits = Gitlab::Satellite::MergeAction.new(self.author, self).commits_between
+ else
+ commits = target_project.repository.commits_between(self.target_branch, self.source_branch)
+ end
+
+ if commits.present?
+ commits = Commit.decorate(commits).
sort_by(&:created_at).
reverse
+ end
+ commits
end
def merge!(user_id)
- self.mark_as_merged!
- Event.create(
- project: self.project,
- action: Event::MERGED,
- target_id: self.id,
- target_type: "MergeRequest",
- author_id: user_id
- )
+ self.author_id_of_changes = user_id
+ self.merge
end
def automerge!(current_user)
@@ -197,7 +215,7 @@ class MergeRequest < ActiveRecord::Base
true
end
rescue
- self.mark_as_unmergable
+ mark_as_unmergeable
false
end
@@ -209,18 +227,74 @@ class MergeRequest < ActiveRecord::Base
# Returns the raw diff for this merge request
#
# see "git diff"
- def to_diff
- project.repo.git.native(:diff, {timeout: 30, raise: true}, "#{target_branch}...#{source_branch}")
+ def to_diff(current_user)
+ Gitlab::Satellite::MergeAction.new(current_user, self).diff_in_satellite
end
# Returns the commit as a series of email patches.
#
# see "git format-patch"
- def to_patch
- project.repo.git.format_patch({timeout: 30, raise: true, stdout: true}, "#{target_branch}..#{source_branch}")
+ def to_patch(current_user)
+ Gitlab::Satellite::MergeAction.new(current_user, self).format_patch
end
def last_commit_short_sha
@last_commit_short_sha ||= last_commit.sha[0..10]
end
+
+ def for_fork?
+ target_project != source_project
+ end
+
+ def disallow_source_branch_removal?
+ (source_project.root_ref? source_branch) || for_fork?
+ end
+
+ def project
+ target_project
+ end
+
+ # Return the set of issues that will be closed if this merge request is accepted.
+ def closes_issues
+ if target_branch == project.default_branch
+ unmerged_commits.map { |c| c.closes_issues(project) }.flatten.uniq.sort_by(&:id)
+ else
+ []
+ end
+ end
+
+ # Mentionable override.
+ def gfm_reference
+ "merge request !#{iid}"
+ end
+
+ private
+
+ def dump_commits(commits)
+ commits.map(&:to_hash)
+ end
+
+ def load_commits(array)
+ array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash)) }
+ end
+
+ def dump_diffs(diffs)
+ if diffs == broken_diffs
+ broken_diffs
+ elsif diffs.respond_to?(:map)
+ diffs.map(&:to_hash)
+ end
+ end
+
+ def load_diffs(raw)
+ if raw == broken_diffs
+ broken_diffs
+ elsif raw.respond_to?(:map)
+ raw.map { |hash| Gitlab::Git::Diff.new(hash) }
+ end
+ end
+
+ def broken_diffs
+ [Gitlab::Git::Diff::BROKEN_DIFF]
+ end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 457fe18f35b..1a73fa71e48 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -7,25 +7,42 @@
# project_id :integer not null
# description :text
# due_date :date
-# closed :boolean default(FALSE), not null
# created_at :datetime not null
# updated_at :datetime not null
+# state :string(255)
+# iid :integer
#
class Milestone < ActiveRecord::Base
- attr_accessible :title, :description, :due_date, :closed, :author_id_of_changes
+ include InternalId
+
+ attr_accessible :title, :description, :due_date, :state_event, :author_id_of_changes
attr_accessor :author_id_of_changes
belongs_to :project
has_many :issues
has_many :merge_requests
+ has_many :participants, through: :issues, source: :assignee
- scope :active, -> { where(closed: false) }
- scope :closed, -> { where(closed: true) }
+ scope :active, -> { with_state(:active) }
+ scope :closed, -> { with_state(:closed) }
validates :title, presence: true
validates :project, presence: true
- validates :closed, inclusion: { in: [true, false] }
+
+ state_machine :state, initial: :active do
+ event :close do
+ transition active: :closed
+ end
+
+ event :activate do
+ transition closed: :active
+ end
+
+ state :closed
+
+ state :active
+ end
def expired?
if due_date
@@ -35,10 +52,6 @@ class Milestone < ActiveRecord::Base
end
end
- def participants
- User.where(id: issues.pluck(:assignee_id))
- end
-
def open_items_count
self.issues.opened.count + self.merge_requests.opened.count
end
@@ -62,23 +75,19 @@ class Milestone < ActiveRecord::Base
if due_date.past?
"expired at #{due_date.stamp("Aug 21, 2011")}"
else
- "expires at #{due_date.stamp("Aug 21, 2011")}"
+ "expires at #{due_date.stamp("Aug 21, 2011")}"
end
- end
+ end
end
def can_be_closed?
- open? && issues.opened.count.zero?
+ active? && issues.opened.count.zero?
end
def is_empty?
total_items_count.zero?
end
- def open?
- !closed
- end
-
def author_id
author_id_of_changes
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 4e157839369..6ac94c06604 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -2,31 +2,39 @@
#
# Table name: namespaces
#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# type :string(255)
+# id :integer not null, primary key
+# name :string(255) not null
+# path :string(255) not null
+# owner_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# type :string(255)
+# description :string(255) default(""), not null
#
class Namespace < ActiveRecord::Base
- attr_accessible :name, :path
+ include Gitlab::ShellAdapter
+
+ attr_accessible :name, :description, :path
has_many :projects, dependent: :destroy
belongs_to :owner, class_name: "User"
- validates :name, presence: true, uniqueness: true
+ validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
+ validates :name, presence: true, uniqueness: true,
+ length: { within: 0..255 },
+ 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 },
+ exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
- validates :owner, presence: true
delegate :name, to: :owner, allow_nil: true, prefix: true
after_create :ensure_dir_exist
- after_update :move_dir
+ after_update :move_dir, if: :path_changed?
after_destroy :rm_dir
scope :root, -> { where('type IS NULL') }
@@ -48,48 +56,34 @@ class Namespace < ActiveRecord::Base
end
def ensure_dir_exist
- unless dir_exists?
- FileUtils.mkdir( namespace_full_path, mode: 0770 )
- end
- end
-
- def dir_exists?
- File.exists?(namespace_full_path)
+ gitlab_shell.add_namespace(path)
end
- def namespace_full_path
- @namespace_full_path ||= File.join(Gitlab.config.gitlab_shell.repos_path, path)
+ def rm_dir
+ gitlab_shell.rm_namespace(path)
end
def move_dir
- if path_changed?
- old_path = File.join(Gitlab.config.gitlab_shell.repos_path, path_was)
- new_path = File.join(Gitlab.config.gitlab_shell.repos_path, path)
- if File.exists?(new_path)
- raise "Already exists"
- end
-
-
+ if gitlab_shell.mv_namespace(path_was, path)
+ # If repositories moved successfully we need to remove old satellites
+ # and send update instructions to users.
+ # However we cannot allow rollback since we moved namespace dir
+ # So we basically we mute exceptions in next actions
begin
- # Remove satellite when moving repo
- if path_was.present?
- satellites_path = File.join(Gitlab.config.satellites.path, path_was)
- FileUtils.rm_r( satellites_path, force: true )
- end
-
- FileUtils.mv( old_path, new_path )
+ gitlab_shell.rm_satellites(path_was)
send_update_instructions
- rescue Exception => e
- raise "Namespace move error #{old_path} #{new_path}"
+ rescue
+ # Returning false does not rollback after_* transaction but gives
+ # us information about failing some of tasks
+ false
end
+ else
+ # if we cannot move namespace directory we should rollback
+ # db changes in order to prevent out of sync between db and fs
+ raise Exception.new('namespace directory cannot be moved')
end
end
- def rm_dir
- dir_path = File.join(Gitlab.config.gitlab_shell.repos_path, path)
- FileUtils.rm_r( dir_path, force: true )
- end
-
def send_update_instructions
projects.each(&:send_move_instructions)
end
diff --git a/app/models/network/commit.rb b/app/models/network/commit.rb
new file mode 100644
index 00000000000..e31adcebbe7
--- /dev/null
+++ b/app/models/network/commit.rb
@@ -0,0 +1,37 @@
+require "grit"
+
+module Network
+ class Commit
+ include ActionView::Helpers::TagHelper
+
+ attr_accessor :time, :spaces, :parent_spaces
+
+ def initialize(raw_commit)
+ @commit = raw_commit
+ @time = -1
+ @spaces = []
+ @parent_spaces = []
+ end
+
+ def method_missing(m, *args, &block)
+ @commit.send(m, *args, &block)
+ end
+
+ def space
+ if @spaces.size > 0
+ @spaces.first
+ else
+ 0
+ end
+ end
+
+ def parents(map)
+ @commit.parents.map do |p|
+ if map.include?(p.id)
+ map[p.id]
+ end
+ end
+ .compact
+ end
+ end
+end
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
new file mode 100644
index 00000000000..424819f3501
--- /dev/null
+++ b/app/models/network/graph.rb
@@ -0,0 +1,273 @@
+module Network
+ class Graph
+ attr_reader :days, :commits, :map, :notes, :repo
+
+ def self.max_count
+ @max_count ||= 650
+ end
+
+ def initialize project, ref, commit, filter_ref
+ @project = project
+ @ref = ref
+ @commit = commit
+ @filter_ref = filter_ref
+ @repo = project.repository
+
+ @commits = collect_commits
+ @days = index_commits
+ @notes = collect_notes
+ end
+
+ protected
+
+ def collect_notes
+ h = Hash.new(0)
+ @project.notes.where('noteable_type = ?' ,"Commit").group('notes.commit_id').select('notes.commit_id, count(notes.id) as note_count').each do |item|
+ h[item.commit_id] = item.note_count.to_i
+ end
+ h
+ end
+
+ # Get commits from repository
+ #
+ def collect_commits
+ find_commits(count_to_display_commit_in_center).map do |commit|
+ # Decorate with app/model/network/commit.rb
+ Network::Commit.new(commit)
+ end
+ end
+
+ # Method is adding time and space on the
+ # list of commits. As well as returns date list
+ # correlated with time set on commits.
+ #
+ # @return [Array<TimeDate>] list of commit dates correlated with time on commits
+ def index_commits
+ days = []
+ @map = {}
+ @reserved = {}
+
+ @commits.each_with_index do |c,i|
+ c.time = i
+ days[i] = c.committed_date
+ @map[c.id] = c
+ @reserved[i] = []
+ end
+
+ commits_sort_by_ref.each do |commit|
+ place_chain(commit)
+ end
+
+ # find parent spaces for not overlap lines
+ @commits.each do |c|
+ c.parent_spaces.concat(find_free_parent_spaces(c))
+ end
+
+ days
+ end
+
+ # Skip count that the target commit is displayed in center.
+ def count_to_display_commit_in_center
+ offset = -1
+ skip = 0
+ while offset == -1
+ tmp_commits = find_commits(skip)
+ if tmp_commits.size > 0
+ index = tmp_commits.index do |c|
+ c.id == @commit.id
+ end
+
+ if index
+ # Find the target commit
+ offset = index + skip
+ else
+ skip += self.class.max_count
+ end
+ else
+ # Cant't find the target commit in the repo.
+ offset = 0
+ end
+ end
+
+ if self.class.max_count / 2 < offset then
+ # get max index that commit is displayed in the center.
+ offset - self.class.max_count / 2
+ else
+ 0
+ end
+ end
+
+ def find_commits(skip = 0)
+ opts = {
+ max_count: self.class.max_count,
+ skip: skip
+ }
+
+ opts[:ref] = @commit.id if @filter_ref
+
+ @repo.find_commits(opts)
+ end
+
+ def commits_sort_by_ref
+ @commits.sort do |a,b|
+ if include_ref?(a)
+ -1
+ elsif include_ref?(b)
+ 1
+ else
+ b.committed_date <=> a.committed_date
+ end
+ end
+ end
+
+ def include_ref?(commit)
+ commit.ref_names(@repo).include?(@ref)
+ end
+
+ def find_free_parent_spaces(commit)
+ spaces = []
+
+ commit.parents(@map).each do |parent|
+ range = commit.time..parent.time
+
+ space = if commit.space >= parent.space then
+ find_free_parent_space(range, parent.space, -1, commit.space)
+ else
+ find_free_parent_space(range, commit.space, -1, parent.space)
+ end
+
+ mark_reserved(range, space)
+ spaces << space
+ end
+
+ spaces
+ end
+
+ def find_free_parent_space(range, space_base, space_step, space_default)
+ if is_overlap?(range, space_default) then
+ find_free_space(range, space_step, space_base, space_default)
+ else
+ space_default
+ end
+ end
+
+ def is_overlap?(range, overlap_space)
+ range.each do |i|
+ if i != range.first &&
+ i != range.last &&
+ @commits[i].spaces.include?(overlap_space) then
+
+ return true;
+ end
+ end
+
+ false
+ end
+
+ # Add space mark on commit and its parents
+ #
+ # @param [::Commit] the commit object.
+ def place_chain(commit, parent_time = nil)
+ leaves = take_left_leaves(commit)
+ if leaves.empty?
+ return
+ end
+
+ time_range = leaves.first.time..leaves.last.time
+ space_base = get_space_base(leaves)
+ space = find_free_space(time_range, 2, space_base)
+ leaves.each do |l|
+ l.spaces << space
+ # Also add space to parent
+ l.parents(@map).each do |parent|
+ if 0 < parent.space && parent.space < space
+ parent.spaces << space
+ end
+ end
+ end
+
+ # and mark it as reserved
+ if parent_time.nil?
+ min_time = leaves.first.time
+ else
+ min_time = parent_time + 1
+ end
+
+ max_time = leaves.last.time
+ leaves.last.parents(@map).each do |parent|
+ if max_time < parent.time
+ max_time = parent.time
+ end
+ end
+ mark_reserved(min_time..max_time, space)
+
+ # Visit branching chains
+ leaves.each do |l|
+ parents = l.parents(@map).select{|p| p.space.zero?}
+ for p in parents
+ place_chain(p, l.time)
+ end
+ end
+ end
+
+ def get_space_base(leaves)
+ space_base = 1
+ parents = leaves.last.parents(@map)
+ if parents.size > 0
+ if parents.first.space > 0
+ space_base = parents.first.space
+ end
+ end
+ space_base
+ end
+
+ def mark_reserved(time_range, space)
+ for day in time_range
+ @reserved[day].push(space)
+ end
+ end
+
+ def find_free_space(time_range, space_step, space_base = 1, space_default = nil)
+ space_default ||= space_base
+
+ reserved = []
+ for day in time_range
+ reserved += @reserved[day]
+ end
+ reserved.uniq!
+
+ space = space_default
+ while reserved.include?(space) do
+ space += space_step
+ if space < space_base then
+ space_step *= -1
+ space = space_base + space_step
+ end
+ end
+
+ space
+ end
+
+ # Takes most left subtree branch of commits
+ # which don't have space mark yet.
+ #
+ # @param [::Commit] the commit object.
+ #
+ # @return [Array<Network::Commit>] list of branch commits
+ def take_left_leaves(raw_commit)
+ commit = @map[raw_commit.id]
+ leaves = []
+ leaves.push(commit) if commit.space.zero?
+
+ while true
+ return leaves if commit.parents(@map).count.zero?
+
+ commit = commit.parents(@map).first
+
+ return leaves unless commit.space.zero?
+
+ leaves.push(commit)
+ end
+ end
+ end
+end
diff --git a/app/models/note.rb b/app/models/note.rb
index 97f6bf6e3a7..e819a5516b5 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -13,17 +13,18 @@
# line_code :string(255)
# commit_id :string(255)
# noteable_id :integer
+# st_diff :text
#
require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Note < ActiveRecord::Base
+ include Mentionable
+
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
:attachment, :line_code, :commit_id
-
- attr_accessor :notify
- attr_accessor :notify_author
+ attr_mentionable :note
belongs_to :project
belongs_to :noteable, polymorphic: true
@@ -43,55 +44,112 @@ class Note < ActiveRecord::Base
# Scopes
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
- scope :inline, -> { where("line_code IS NOT NULL") }
- scope :not_inline, -> { where("line_code IS NULL") }
+ scope :inline, ->{ where("line_code IS NOT NULL") }
+ scope :not_inline, ->{ where(line_code: [nil, '']) }
scope :common, ->{ where(noteable_type: ["", nil]) }
scope :fresh, ->{ order("created_at ASC, id ASC") }
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
- def self.create_status_change_note(noteable, author, status)
+ serialize :st_diff
+ before_create :set_diff, if: ->(n) { n.line_code.present? }
+
+ def self.create_status_change_note(noteable, project, author, status, source)
+ body = "_Status changed to #{status}#{' by ' + source.gfm_reference if source}_"
+
+ create({
+ noteable: noteable,
+ project: project,
+ author: author,
+ note: body,
+ system: true
+ }, without_protection: true)
+ end
+
+ # +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note.
+ # Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+.
+ def self.create_cross_reference_note(noteable, mentioner, author, project)
create({
noteable: noteable,
- project: noteable.project,
+ commit_id: (noteable.sha if noteable.respond_to? :sha),
+ project: project,
author: author,
- note: "_Status changed to #{status}_"
+ note: "_mentioned in #{mentioner.gfm_reference}_",
+ system: true
}, without_protection: true)
end
+ # Determine whether or not a cross-reference note already exists.
+ def self.cross_reference_exists?(noteable, mentioner)
+ where(noteable_id: noteable.id, system: true, note: "_mentioned in #{mentioner.gfm_reference}_").any?
+ end
+
def commit_author
@commit_author ||=
project.users.find_by_email(noteable.author_email) ||
- project.users.find_by_name(noteable.author_name)
+ project.users.find_by_name(noteable.author_name)
rescue
nil
end
- def diff
- if noteable.diffs.present?
- noteable.diffs.select do |d|
- if d.b_path
- Digest::SHA1.hexdigest(d.b_path) == diff_file_index
- end
- end.first
+ def find_diff
+ return nil unless noteable && noteable.diffs.present?
+
+ @diff ||= noteable.diffs.find do |d|
+ Digest::SHA1.hexdigest(d.new_path) == diff_file_index if d.new_path
end
end
+ def set_diff
+ # First lets find notes with same diff
+ # before iterating over all mr diffs
+ diff = Note.where(noteable_id: self.noteable_id, noteable_type: self.noteable_type, line_code: self.line_code).last.try(:diff)
+ diff ||= find_diff
+
+ self.st_diff = diff.to_hash if diff
+ end
+
+ def diff
+ @diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
+ end
+
+ def active?
+ # TODO: determine if discussion is outdated
+ # according to recent MR diff or not
+ true
+ end
+
def diff_file_index
line_code.split('_')[0]
end
def diff_file_name
- diff.b_path
+ diff.new_path if diff
+ end
+
+ def diff_old_line
+ line_code.split('_')[1].to_i
end
def diff_new_line
line_code.split('_')[2].to_i
end
+ def diff_line
+ return @diff_line if @diff_line
+
+ if diff
+ Gitlab::DiffParser.new(diff).each do |full_line, type, line_code, line_new, line_old|
+ @diff_line = full_line if line_code == self.line_code
+ end
+ end
+
+ @diff_line
+ end
+
def discussion_id
- @discussion_id ||= [:discussion, noteable_type.try(:underscore), noteable_id, line_code].join("-").to_sym
+ @discussion_id ||= [:discussion, noteable_type.try(:underscore), noteable_id || commit_id, line_code].join("-").to_sym
end
# Returns true if this is a downvote note,
@@ -138,19 +196,11 @@ class Note < ActiveRecord::Base
super
end
# Temp fix to prevent app crash
- # if note commit id doesnt exist
+ # if note commit id doesn't exist
rescue
nil
end
- def notify
- @notify ||= false
- end
-
- def notify_author
- @notify_author ||= false
- end
-
# Returns true if this is an upvote note,
# otherwise false is returned
def upvote?
@@ -163,6 +213,16 @@ class Note < ActiveRecord::Base
for_issue? || (for_merge_request? && !for_diff_line?)
end
+ # Mentionable override.
+ def gfm_reference
+ noteable.gfm_reference
+ end
+
+ # Mentionable override.
+ def local_reference
+ noteable
+ end
+
def noteable_type_name
if noteable_type.present?
noteable_type.downcase
@@ -170,4 +230,10 @@ class Note < ActiveRecord::Base
"wall"
end
end
+
+ # FIXME: Hack for polymorphic associations with STI
+ # For more information wisit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
+ def noteable_type=(sType)
+ super(sType.to_s.classify.constantize.base_class.to_s)
+ end
end
diff --git a/app/models/notification.rb b/app/models/notification.rb
new file mode 100644
index 00000000000..ff6a18d6a51
--- /dev/null
+++ b/app/models/notification.rb
@@ -0,0 +1,39 @@
+class Notification
+ #
+ # Notification levels
+ #
+ N_DISABLED = 0
+ N_PARTICIPATING = 1
+ N_WATCH = 2
+ N_GLOBAL = 3
+
+ 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]
+ end
+
+ def initialize(target)
+ @target = target
+ end
+
+ def disabled?
+ target.notification_level == N_DISABLED
+ end
+
+ def participating?
+ target.notification_level == N_PARTICIPATING
+ end
+
+ def watch?
+ target.notification_level == N_WATCH
+ end
+
+ def global?
+ target.notification_level == N_GLOBAL
+ end
+end
diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb
new file mode 100644
index 00000000000..ef2000ad05e
--- /dev/null
+++ b/app/models/personal_snippet.rb
@@ -0,0 +1,19 @@
+# == Schema Information
+#
+# Table name: snippets
+#
+# id :integer not null, primary key
+# title :string(255)
+# content :text(2147483647)
+# author_id :integer not null
+# project_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# file_name :string(255)
+# expires_at :datetime
+# private :boolean default(TRUE), not null
+# type :string(255)
+#
+
+class PersonalSnippet < Snippet
+end
diff --git a/app/models/pivotaltracker_service.rb b/app/models/pivotaltracker_service.rb
new file mode 100644
index 00000000000..f28e142da77
--- /dev/null
+++ b/app/models/pivotaltracker_service.rb
@@ -0,0 +1,59 @@
+# == 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
+#
+
+class PivotaltrackerService < Service
+ include HTTParty
+
+ validates :token, presence: true, if: :activated?
+
+ def title
+ 'PivotalTracker'
+ end
+
+ def description
+ 'Project Management Software (Source Commits Endpoint)'
+ end
+
+ def to_param
+ 'pivotaltracker'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'token', placeholder: '' }
+ ]
+ end
+
+ def execute(push)
+ url = 'https://www.pivotaltracker.com/services/v5/source_commits'
+ push[:commits].each do |commit|
+ message = {
+ 'source_commit' => {
+ 'commit_id' => commit[:id],
+ 'author' => commit[:author][:name],
+ 'url' => commit[:url],
+ 'message' => commit[:message]
+ }
+ }
+ PivotaltrackerService.post(
+ url,
+ body: message.to_json,
+ headers: {
+ 'Content-Type' => 'application/json',
+ 'X-TrackerToken' => token
+ }
+ )
+ end
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 15b2d858b62..c4f4b06713d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -16,22 +16,27 @@
# wiki_enabled :boolean default(TRUE), not null
# namespace_id :integer
# public :boolean default(FALSE), not null
+# issues_tracker :string(255) default("gitlab"), not null
+# issues_tracker_id :string(255)
+# snippets_enabled :boolean default(TRUE), not null
+# last_activity_at :datetime
+# imported :boolean default(FALSE), not null
+# import_url :string(255)
#
require "grit"
class Project < ActiveRecord::Base
- include Gitolited
+ include Gitlab::ShellAdapter
+ extend Enumerize
- class TransferError < StandardError; end
-
- attr_accessible :name, :path, :description, :default_branch,
- :issues_enabled, :wall_enabled, :merge_requests_enabled,
- :wiki_enabled, :public, :import_url, as: [:default, :admin]
+ attr_accessible :name, :path, :description, :default_branch, :issues_tracker, :label_list,
+ :issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id,
+ :wiki_enabled, :public, :import_url, :last_activity_at, as: [:default, :admin]
attr_accessible :namespace_id, :creator_id, as: :admin
- attr_accessor :import_url
+ acts_as_taggable_on :labels, :issues_default_labels
# Relations
belongs_to :creator, foreign_key: "creator_id", class_name: "User"
@@ -40,66 +45,72 @@ class Project < ActiveRecord::Base
has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id'
has_one :gitlab_ci_service, dependent: :destroy
+ has_one :campfire_service, dependent: :destroy
+ has_one :pivotaltracker_service, dependent: :destroy
+ has_one :hipchat_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
+ has_many :services, dependent: :destroy
has_many :events, dependent: :destroy
- has_many :merge_requests, dependent: :destroy
- has_many :issues, dependent: :destroy, order: "closed, created_at DESC"
+ has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
+ has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC"
has_many :milestones, dependent: :destroy
- has_many :users_projects, dependent: :destroy
has_many :notes, dependent: :destroy
- has_many :snippets, dependent: :destroy
- has_many :deploy_keys, dependent: :destroy, class_name: "Key", foreign_key: "project_id"
+ has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
- has_many :wikis, dependent: :destroy
has_many :protected_branches, dependent: :destroy
- has_many :user_team_project_relationships, dependent: :destroy
- has_many :users, through: :users_projects
- has_many :user_teams, through: :user_team_project_relationships
- has_many :user_team_user_relationships, through: :user_teams
- has_many :user_teams_members, through: :user_team_user_relationships
+ 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
delegate :name, to: :owner, allow_nil: true, prefix: true
+ delegate :members, to: :team, prefix: true
# Validations
validates :creator, presence: true
validates :description, length: { within: 0..2000 }
validates :name, presence: true, length: { within: 0..255 },
format: { with: Gitlab::Regex.project_name_regex,
- message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter should be first" }
+ message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter or digit should be first" }
validates :path, presence: true, length: { within: 0..255 },
+ exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.path_regex,
- message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
+ message: "only letters, digits & '_' '-' '.' allowed. Letter or digit should be first" }
validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] }
+ validates :issues_tracker_id, length: { within: 0..255 }
+ 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(http https)), message: "should be a valid url" },
+ format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" },
if: :import?
- validate :check_limit, :repo_name
+ validate :check_limit, on: :create
# Scopes
- scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
- scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) }
- scope :without_team, ->(team) { team.projects.present? ? where("id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped }
- scope :in_team, ->(team) { where("id IN (:ids)", ids: team.projects.map(&:id)) }
+ scope :without_user, ->(user) { where("projects.id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
+ scope :without_team, ->(team) { team.projects.present? ? where("projects.id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped }
+ scope :not_in_group, ->(group) { where("projects.id NOT IN (:ids)", ids: group.project_ids ) }
+ scope :in_team, ->(team) { where("projects.id IN (:ids)", ids: team.projects.map(&:id)) }
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
- scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") }
+ scope :in_group_namespace, -> { joins(:group) }
+ scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
scope :public_only, -> { where(public: true) }
+ enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
+
class << self
def abandoned
- project_ids = Event.select('max(created_at) as latest_date, project_id').
- group('project_id').
- having('latest_date < ?', 6.months.ago).map(&:project_id)
-
- where(id: project_ids)
+ where('projects.last_activity_at < ?', 6.months.ago)
end
def with_push
@@ -125,10 +136,6 @@ class Project < ActiveRecord::Base
where(path: id, namespace_id: nil).last
end
end
-
- def access_options
- UsersProject.access_roles
- end
end
def team
@@ -136,45 +143,31 @@ class Project < ActiveRecord::Base
end
def repository
- if path
- @repository ||= Repository.new(path_with_namespace, default_branch)
- else
- nil
- end
- rescue Grit::NoSuchPathError
- nil
+ @repository ||= Repository.new(path_with_namespace, default_branch)
end
def saved?
- id && valid?
+ id && persisted?
end
def import?
import_url.present?
end
+ def imported?
+ imported
+ end
+
def check_limit
unless creator.can_create_project?
- errors[:base] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it")
+ errors[:limit_reached] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it")
end
rescue
errors[:base] << ("Can't check your ability to create project")
end
- def repo_name
- denied_paths = %w(admin dashboard groups help profile projects search)
-
- if denied_paths.include?(path)
- errors.add(:path, "like #{path} is not allowed")
- end
- end
-
def to_param
- if namespace
- namespace.path + "/" + path
- else
- path
- end
+ namespace.path + "/" + path
end
def web_url
@@ -190,7 +183,7 @@ class Project < ActiveRecord::Base
end
def last_activity_date
- last_event.try(:created_at) || updated_at
+ last_activity_at || updated_at
end
def project_id
@@ -198,11 +191,37 @@ class Project < ActiveRecord::Base
end
def issues_labels
- issues.tag_counts_on(:labels)
+ @issues_labels ||= (issues_default_labels + issues.tags_on(:labels)).uniq.sort_by(&:name)
+ end
+
+ def issue_exists?(issue_id)
+ if used_default_issues_tracker?
+ self.issues.where(iid: issue_id).first.present?
+ else
+ true
+ end
+ end
+
+ def used_default_issues_tracker?
+ self.issues_tracker == Project.issues_tracker.default_value
end
- def services
- [gitlab_ci_service].compact
+ def can_have_issues_tracker_id?
+ self.issues_enabled && !self.used_default_issues_tracker?
+ end
+
+ def build_missing_services
+ available_services_names.each do |service_name|
+ service = services.find { |service| service.to_param == service_name }
+
+ # If service is available but missing in db
+ # we should create an instance. Ex `create_gitlab_ci_service`
+ service = self.send :"create_#{service_name}_service" if service.nil?
+ end
+ end
+
+ def available_services_names
+ %w(gitlab_ci campfire hipchat pivotaltracker)
end
def gitlab_ci?
@@ -224,16 +243,16 @@ class Project < ActiveRecord::Base
end
def send_move_instructions
- self.users_projects.each do |member|
- Notify.delay.project_was_moved_email(member.id)
+ team.members.each do |user|
+ Notify.delay.project_was_moved_email(self.id, user.id)
end
end
def owner
- if namespace
- namespace_owner
+ if group
+ group
else
- creator
+ namespace.try(:owner)
end
end
@@ -247,32 +266,6 @@ class Project < ActiveRecord::Base
users_projects.find_by_user_id(user_id)
end
- def transfer(new_namespace)
- Project.transaction do
- old_namespace = namespace
- self.namespace = new_namespace
-
- old_dir = old_namespace.try(:path) || ''
- new_dir = new_namespace.try(:path) || ''
-
- old_repo = if old_dir.present?
- File.join(old_dir, self.path)
- else
- self.path
- end
-
- if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present?
- raise TransferError.new("Project with same path in target namespace already exists")
- end
-
- Gitlab::ProjectMover.new(self, old_dir, new_dir).execute
-
- save!
- end
- rescue Gitlab::ProjectMover::ProjectMoveError => ex
- raise Project::TransferError.new(ex.message)
- end
-
def name_with_namespace
@name_with_namespace ||= begin
if namespace
@@ -283,10 +276,6 @@ class Project < ActiveRecord::Base
end
end
- def namespace_owner
- namespace.try(:owner)
- end
-
def path_with_namespace
if namespace
namespace.path + '/' + path
@@ -295,51 +284,8 @@ class Project < ActiveRecord::Base
end
end
- # This method will be called after each post receive and only if the provided
- # user is present in GitLab.
- #
- # All callbacks for post receive should be placed here.
- def trigger_post_receive(oldrev, newrev, ref, user)
- data = post_receive_data(oldrev, newrev, ref, user)
-
- # Create satellite
- self.satellite.create unless self.satellite.exists?
-
- # Create push event
- self.observe_push(data)
-
- if push_to_branch? ref, oldrev
- # Close merged MR
- self.update_merge_requests(oldrev, newrev, ref, user)
-
- # Execute web hooks
- self.execute_hooks(data.dup)
-
- # Execute project services
- self.execute_services(data.dup)
- end
-
- # Discover the default branch, but only if it hasn't already been set to
- # something else
- if repository && default_branch.nil?
- update_attributes(default_branch: self.repository.discover_default_branch)
- end
- end
-
- def push_to_branch? ref, oldrev
- ref_parts = ref.split('/')
-
- # Return if this is not a push to a branch (e.g. new commits)
- !(ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000")
- end
-
- def observe_push(data)
- Event.create(
- project: self,
- action: Event::PUSHED,
- data: data,
- author_id: data[:user_id]
- )
+ def transfer(new_namespace)
+ ProjectTransferService.new.transfer(self, new_namespace)
end
def execute_hooks(data)
@@ -354,68 +300,12 @@ class Project < ActiveRecord::Base
end
end
- # Produce a hash of post-receive data
- #
- # data = {
- # before: String,
- # after: String,
- # ref: String,
- # user_id: String,
- # user_name: String,
- # repository: {
- # name: String,
- # url: String,
- # description: String,
- # homepage: String,
- # },
- # commits: Array,
- # total_commits_count: Fixnum
- # }
- #
- def post_receive_data(oldrev, newrev, ref, user)
-
- push_commits = repository.commits_between(oldrev, newrev)
-
- # Total commits count
- push_commits_count = push_commits.size
-
- # Get latest 20 commits ASC
- push_commits_limited = push_commits.last(20)
-
- # Hash to be passed as post_receive_data
- data = {
- before: oldrev,
- after: newrev,
- ref: ref,
- user_id: user.id,
- user_name: user.name,
- repository: {
- name: name,
- url: url_to_repo,
- description: description,
- homepage: web_url,
- },
- commits: [],
- total_commits_count: push_commits_count
- }
-
- # For perfomance purposes maximum 20 latest commits
- # will be passed as post receive hook data.
- #
- push_commits_limited.each do |commit|
- data[:commits] << {
- id: commit.id,
- message: commit.safe_message,
- timestamp: commit.date.xmlschema,
- url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}",
- author: {
- name: commit.author_name,
- email: commit.author_email
- }
- }
+ def discover_default_branch
+ # Discover the default branch, but only if it hasn't already been set to
+ # something else
+ if repository.exists? && default_branch.nil?
+ update_attributes(default_branch: self.repository.discover_default_branch)
end
-
- data
end
def update_merge_requests(oldrev, newrev, ref, user)
@@ -424,7 +314,7 @@ class Project < ActiveRecord::Base
c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
# Update code for merge requests
- mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all
+ mrs = self.merge_requests.opened.by_branch(branch_name).all
mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
# Close merge requests
@@ -436,14 +326,18 @@ class Project < ActiveRecord::Base
end
def valid_repo?
- repo
+ repository.exists?
rescue
errors.add(:path, "Invalid repository path")
false
end
def empty_repo?
- !repository || repository.empty?
+ !repository.exists? || repository.empty?
+ end
+
+ def ensure_satellite_exists
+ self.satellite.create unless self.satellite.exists?
end
def satellite
@@ -463,18 +357,25 @@ class Project < ActiveRecord::Base
end
def repo_exists?
- @repo_exists ||= (repository && repository.branches.present?)
+ @repo_exists ||= repository.exists?
rescue
@repo_exists = false
end
def open_branches
- if protected_branches.empty?
- self.repo.heads
- else
- pnames = protected_branches.map(&:name)
- self.repo.heads.reject { |h| pnames.include?(h.name) }
- end.sort_by(&:name)
+ all_branches = repository.branches
+
+ if protected_branches.present?
+ all_branches.reject! do |branch|
+ protected_branches_names.include?(branch.name)
+ end
+ end
+
+ all_branches
+ end
+
+ def protected_branches_names
+ @protected_branches_names ||= protected_branches.map(&:name)
end
def root_ref?(branch)
@@ -489,13 +390,58 @@ class Project < ActiveRecord::Base
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end
- def project_access_human(member)
- project_user_relation = self.users_projects.find_by_user_id(member.id)
- self.class.access_options.key(project_user_relation.project_access)
- end
-
# Check if current branch name is marked as protected in the system
def protected_branch? branch_name
- protected_branches.map(&:name).include?(branch_name)
+ protected_branches_names.include?(branch_name)
+ end
+
+ def forked?
+ !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
+ end
+
+ def personal?
+ !group
+ end
+
+ def rename_repo
+ old_path_with_namespace = File.join(namespace_dir, path_was)
+ new_path_with_namespace = File.join(namespace_dir, path)
+
+ if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
+ # If repository moved successfully we need to remove old satellite
+ # and send update instructions to users.
+ # However we cannot allow rollback since we moved repository
+ # So we basically we mute exceptions in next actions
+ begin
+ gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
+ gitlab_shell.rm_satellites(old_path_with_namespace)
+ ensure_satellite_exists
+ send_move_instructions
+ reset_events_cache
+ rescue
+ # Returning false does not rollback after_* transaction but gives
+ # us information about failing some of tasks
+ false
+ end
+ else
+ # if we cannot move namespace directory we should rollback
+ # db changes in order to prevent out of sync between db and fs
+ raise Exception.new('repository cannot be renamed')
+ end
+ end
+
+ # Reset events cache related to this project
+ #
+ # Since we do cache @event we need to reset cache in special cases:
+ # * when project was moved
+ # * when project was renamed
+ # Events cache stored like events/23-20130109142513.
+ # The cache key includes updated_at timestamp.
+ # Thus it will automatically generate a new fragment
+ # when the event is updated because the key changes.
+ def reset_events_cache
+ Event.where(project_id: self.id).
+ order('id DESC').limit(100).
+ update_all(updated_at: Time.now)
end
end
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
new file mode 100644
index 00000000000..f38aa07059c
--- /dev/null
+++ b/app/models/project_snippet.rb
@@ -0,0 +1,28 @@
+# == Schema Information
+#
+# Table name: snippets
+#
+# id :integer not null, primary key
+# title :string(255)
+# content :text(2147483647)
+# author_id :integer not null
+# project_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# file_name :string(255)
+# expires_at :datetime
+# private :boolean default(TRUE), not null
+# type :string(255)
+#
+
+class ProjectSnippet < Snippet
+ belongs_to :project
+ belongs_to :author, class_name: "User"
+
+ validates :project, presence: true
+
+ # Scopes
+ scope :fresh, -> { order("created_at DESC") }
+ scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
+ scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
+end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index c2cf83c0ca8..bc35c4041ba 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -21,8 +21,26 @@ class ProjectTeam
end
end
- def get_tm user_id
- project.users_projects.find_by_user_id(user_id)
+ def find(user_id)
+ user = project.users.find_by_id(user_id)
+
+ if group
+ user ||= group.users.find_by_id(user_id)
+ end
+
+ user
+ end
+
+ def find_tm(user_id)
+ tm = project.users_projects.find_by_user_id(user_id)
+
+ # If user is not in project members
+ # we should check for group membership
+ if group && !tm
+ tm = group.users_groups.find_by_user_id(user_id)
+ end
+
+ tm
end
def add_user(user, access)
@@ -47,45 +65,23 @@ class ProjectTeam
end
def members
- project.users_projects
+ @members ||= fetch_members
end
def guests
- members.guests.map(&:user)
+ @guests ||= fetch_members(:guests)
end
def reporters
- members.reporters.map(&:user)
+ @reporters ||= fetch_members(:reporters)
end
def developers
- members.developers.map(&:user)
+ @developers ||= fetch_members(:developers)
end
def masters
- members.masters.map(&:user)
- end
-
- def repository_readers
- repository_members[UsersProject::REPORTER]
- end
-
- def repository_writers
- repository_members[UsersProject::DEVELOPER]
- end
-
- def repository_masters
- repository_members[UsersProject::MASTER]
- end
-
- def repository_members
- keys = Hash.new {|h,k| h[k] = [] }
- UsersProject.select("keys.identifier, project_access").
- joins(user: :keys).where(project_id: project.id).
- each {|row| keys[row.project_access] << [row.identifier] }
-
- keys[UsersProject::REPORTER] += project.deploy_keys.pluck(:identifier)
- keys
+ @masters ||= fetch_members(:masters)
end
def import(source_project)
@@ -104,7 +100,6 @@ class ProjectTeam
new_tm = tm.dup
new_tm.id = nil
new_tm.project_id = target_project.id
- new_tm.skip_git = true
new_tm
end
@@ -118,4 +113,22 @@ class ProjectTeam
rescue
false
end
+
+ private
+
+ def fetch_members(level = nil)
+ project_members = project.users_projects
+ group_members = group ? group.users_groups : []
+
+ if level
+ project_members = project_members.send(level)
+ group_members = group_members.send(level) if group
+ end
+
+ (project_members + group_members).map(&:user).uniq
+ end
+
+ def group
+ project.group
+ end
end
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 57229d50759..16379720e59 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -10,7 +10,7 @@
#
class ProtectedBranch < ActiveRecord::Base
- include Gitolited
+ include Gitlab::ShellAdapter
attr_accessible :name
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 8bcafbacda1..aeec48ee5cc 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1,150 +1,153 @@
class Repository
- # Repository directory name with namespace direcotry
- # Examples:
- # gitlab/gitolite
- # diaspora
- #
- attr_accessor :path_with_namespace
+ include Gitlab::ShellAdapter
- # Grit repo object
- attr_accessor :repo
+ attr_accessor :raw_repository
- # Default branch in the repository
- attr_accessor :root_ref
+ def initialize(path_with_namespace, default_branch)
+ @raw_repository = Gitlab::Git::Repository.new(path_with_namespace, default_branch)
+ rescue Gitlab::Git::Repository::NoRepository
+ nil
+ end
- def initialize(path_with_namespace, root_ref = 'master')
- @root_ref = root_ref || "master"
- @path_with_namespace = path_with_namespace
+ def exists?
+ raw_repository
+ end
- # Init grit repo object
- repo
+ def empty?
+ raw_repository.empty?
end
- def raw
- repo
+ def commit(id = nil)
+ return nil unless raw_repository
+ commit = Gitlab::Git::Commit.find(raw_repository, id)
+ commit = Commit.new(commit) if commit
+ commit
end
- def path_to_repo
- @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
+ def commits(ref, path = nil, limit = nil, offset = nil)
+ commits = Gitlab::Git::Commit.where(
+ repo: raw_repository,
+ ref: ref,
+ path: path,
+ limit: limit,
+ offset: offset,
+ )
+ commits = Commit.decorate(commits) if commits.present?
+ commits
end
- def repo
- @repo ||= Grit::Repo.new(path_to_repo)
+ def commits_between(from, to)
+ commits = Gitlab::Git::Commit.between(raw_repository, from, to)
+ commits = Commit.decorate(commits) if commits.present?
+ commits
end
- def commit(commit_id = nil)
- Commit.find_or_first(repo, commit_id, root_ref)
+ def find_branch(name)
+ branches.find { |branch| branch.name == name }
end
- def fresh_commits(n = 10)
- Commit.fresh_commits(repo, n)
+ def find_tag(name)
+ tags.find { |tag| tag.name == name }
end
- def commits_with_refs(n = 20)
- Commit.commits_with_refs(repo, n)
+ def recent_branches(limit = 20)
+ branches.sort do |a, b|
+ a.commit.committed_date <=> b.commit.committed_date
+ end[0..limit]
end
- def commits_since(date)
- Commit.commits_since(repo, date)
+ def add_branch(branch_name, ref)
+ Rails.cache.delete(cache_key(:branch_names))
+
+ gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
end
- def commits(ref, path = nil, limit = nil, offset = nil)
- Commit.commits(repo, ref, path, limit, offset)
+ def add_tag(tag_name, ref)
+ Rails.cache.delete(cache_key(:tag_names))
+
+ gitlab_shell.add_tag(path_with_namespace, tag_name, ref)
end
- def last_commit_for(ref, path = nil)
- commits(ref, path, 1).first
+ def rm_branch(branch_name)
+ Rails.cache.delete(cache_key(:branch_names))
+
+ gitlab_shell.rm_branch(path_with_namespace, branch_name)
end
- def commits_between(from, to)
- Commit.commits_between(repo, from, to)
+ def rm_tag(tag_name)
+ Rails.cache.delete(cache_key(:tag_names))
+
+ gitlab_shell.rm_tag(path_with_namespace, tag_name)
end
- # Returns an Array of branch names
- def branch_names
- repo.branches.collect(&:name).sort
+ def round_commit_count
+ if commit_count > 10000
+ '10000+'
+ elsif commit_count > 5000
+ '5000+'
+ elsif commit_count > 1000
+ '1000+'
+ else
+ commit_count
+ end
end
- # Returns an Array of Branches
- def branches
- repo.branches.sort_by(&:name)
+ def branch_names
+ Rails.cache.fetch(cache_key(:branch_names)) do
+ raw_repository.branch_names
+ end
end
- # Returns an Array of tag names
def tag_names
- repo.tags.collect(&:name).sort.reverse
+ Rails.cache.fetch(cache_key(:tag_names)) do
+ raw_repository.tag_names
+ end
end
- # Returns an Array of Tags
- def tags
- repo.tags.sort_by(&:name).reverse
+ def commit_count
+ Rails.cache.fetch(cache_key(:commit_count)) do
+ begin
+ raw_repository.raw.commit_count
+ rescue
+ 0
+ end
+ end
end
- # Returns an Array of branch and tag names
- def ref_names
- [branch_names + tag_names].flatten
+ # Return repo size in megabytes
+ # Cached in redis
+ def size
+ Rails.cache.fetch(cache_key(:size)) do
+ raw_repository.size
+ end
end
- def heads
- @heads ||= repo.heads
+ def expire_cache
+ Rails.cache.delete(cache_key(:size))
+ Rails.cache.delete(cache_key(:branch_names))
+ Rails.cache.delete(cache_key(:tag_names))
+ Rails.cache.delete(cache_key(:commit_count))
+ Rails.cache.delete(cache_key(:graph_log))
end
- def tree(fcommit, path = nil)
- fcommit = commit if fcommit == :head
- tree = fcommit.tree
- path ? (tree / path) : tree
+ def graph_log
+ Rails.cache.fetch(cache_key(:graph_log)) do
+ stats = Gitlab::Git::GitStats.new(raw, root_ref)
+ stats.parsed_log
+ end
end
- def has_commits?
- !!commit
- rescue Grit::NoSuchPathError
- false
+ def cache_key(type)
+ "#{type}:#{path_with_namespace}"
end
- def empty?
- !has_commits?
- end
-
- # Discovers the default branch based on the repository's available branches
- #
- # - If no branches are present, returns nil
- # - If one branch is present, returns its name
- # - If two or more branches are present, returns the one that has a name
- # matching root_ref (default_branch or 'master' if default_branch is nil)
- def discover_default_branch
- if branch_names.length == 0
- nil
- elsif branch_names.length == 1
- branch_names.first
- else
- branch_names.select { |v| v == root_ref }.first
- end
+ def method_missing(m, *args, &block)
+ raw_repository.send(m, *args, &block)
end
- # Archive Project to .tar.gz
- #
- # Already packed repo archives stored at
- # app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz
- #
- def archive_repo(ref)
- ref = ref || self.root_ref
- commit = self.commit(ref)
- return nil unless commit
-
- # Build file path
- file_name = self.path_with_namespace + "-" + commit.id.to_s + ".tar.gz"
- storage_path = Rails.root.join("tmp", "repositories")
- file_path = File.join(storage_path, file_name)
-
- # Put files into a directory before archiving
- prefix = self.path_with_namespace + "/"
-
- # Create file if not exists
- unless File.exists?(file_path)
- FileUtils.mkdir_p File.dirname(file_path)
- file = self.repo.archive_to_file(ref, prefix, file_path)
- end
+ def respond_to?(method)
+ return true if raw_repository.respond_to?(method)
- file_path
+ super
end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index d3486d29200..540aaad1ce5 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -11,8 +11,12 @@
# updated_at :datetime not null
# active :boolean default(FALSE), not null
# project_url :string(255)
+# subdomain :string(255)
+# room :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
@@ -24,4 +28,29 @@ class Service < ActiveRecord::Base
def activated?
active
end
+
+ def title
+ # implement inside child
+ end
+
+ def description
+ # implement inside child
+ end
+
+ def to_param
+ # implement inside child
+ end
+
+ def fields
+ # implement inside child
+ []
+ end
+
+ def execute
+ # implement inside child
+ end
+
+ def can_test?
+ !project.empty_repo?
+ end
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index c4ee35e0556..edc179b20fd 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -4,36 +4,39 @@
#
# id :integer not null, primary key
# title :string(255)
-# content :text
+# content :text(2147483647)
# author_id :integer not null
-# project_id :integer not null
+# project_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# file_name :string(255)
# expires_at :datetime
+# private :boolean default(TRUE), not null
+# type :string(255)
#
class Snippet < ActiveRecord::Base
include Linguist::BlobHelper
- attr_accessible :title, :content, :file_name, :expires_at
+ attr_accessible :title, :content, :file_name, :expires_at, :private
- belongs_to :project
belongs_to :author, class_name: "User"
+
has_many :notes, as: :noteable, dependent: :destroy
delegate :name, :email, to: :author, prefix: true, allow_nil: true
validates :author, presence: true
- validates :project, presence: true
validates :title, presence: true, length: { within: 0..255 }
validates :file_name, presence: true, length: { within: 0..255 }
validates :content, presence: true
# Scopes
- scope :fresh, -> { order("created_at DESC") }
- scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
+ scope :public, -> { where(private: false) }
+ scope :private, -> { where(private: true) }
+ scope :fresh, -> { order("created_at DESC") }
scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
+ scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
def self.content_types
[
diff --git a/app/models/system_hook.rb b/app/models/system_hook.rb
index 5f1bd6477c4..5cdf046644f 100644
--- a/app/models/system_hook.rb
+++ b/app/models/system_hook.rb
@@ -12,13 +12,4 @@
#
class SystemHook < WebHook
- def self.all_hooks_fire(data)
- SystemHook.all.each do |sh|
- sh.async_execute data
- end
- end
-
- def async_execute(data)
- Sidekiq::Client.enqueue(SystemHookWorker, id, data)
- end
end
diff --git a/app/models/tree.rb b/app/models/tree.rb
index 96395a42394..042050527c1 100644
--- a/app/models/tree.rb
+++ b/app/models/tree.rb
@@ -1,29 +1,17 @@
class Tree
- include Linguist::BlobHelper
+ attr_accessor :raw
- attr_accessor :path, :tree, :ref
-
- delegate :contents, :basename, :name, :data, :mime_type,
- :mode, :size, :text?, :colorize, to: :tree
-
- def initialize(raw_tree, ref = nil, path = nil)
- @ref, @path = ref, path
- @tree = if path.present?
- raw_tree / path
- else
- raw_tree
- end
+ def initialize(repository, sha, ref = nil, path = nil)
+ @raw = Gitlab::Git::Tree.new(repository, sha, ref, path)
end
- def is_blob?
- tree.is_a?(Grit::Blob)
+ def method_missing(m, *args, &block)
+ @raw.send(m, *args, &block)
end
- def invalid?
- tree.nil?
- end
+ def respond_to?(method)
+ return true if @raw.respond_to?(method)
- def empty?
- data.blank?
+ super
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 10af9b8c165..f1f93eadc1a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -22,10 +22,8 @@
# linkedin :string(255) default(""), not null
# twitter :string(255) default(""), not null
# authentication_token :string(255)
-# dark_scheme :boolean default(FALSE), not null
# theme_id :integer default(1), not null
# bio :string(255)
-# blocked :boolean default(FALSE), not null
# failed_attempts :integer default(0)
# locked_at :datetime
# extern_uid :string(255)
@@ -33,6 +31,11 @@
# username :string(255)
# can_create_group :boolean default(TRUE), not null
# can_create_team :boolean default(TRUE), not null
+# state :string(255)
+# color_scheme_id :integer default(1), not null
+# notification_level :integer default(1), not null
+# password_expires_at :datetime
+# created_by_id :integer
#
class User < ActiveRecord::Base
@@ -40,65 +43,140 @@ class User < ActiveRecord::Base
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :registerable
attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
- :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password,
- :extern_uid, :provider, as: [:default, :admin]
- attr_accessible :projects_limit, :can_create_team, :can_create_group, as: :admin
+ :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password,
+ :extern_uid, :provider, :password_expires_at,
+ as: [:default, :admin]
+
+ attr_accessible :projects_limit, :can_create_group,
+ as: :admin
attr_accessor :force_random_password
+ # Virtual attribute for authenticating by either username or email
+ attr_accessor :login
+
+ # Add login to attr_accessible
+ attr_accessible :login
+
+
+ #
+ # Relations
+ #
+
# Namespace for personal projects
- has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL'
+ has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL'
+
+ # Namespaces (owned groups and own namespace)
+ has_many :namespaces, foreign_key: :owner_id
+
+ # Profile
+ has_many :keys, dependent: :destroy
- has_many :keys, dependent: :destroy
+ # Groups
+ has_many :own_groups, class_name: "Group", foreign_key: :owner_id
+ has_many :owned_groups, through: :users_groups, source: :group, conditions: { users_groups: { group_access: UsersGroup::OWNER } }
+
+ has_many :users_groups, dependent: :destroy
+ has_many :groups, through: :users_groups
+
+ # Projects
+ has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet"
has_many :users_projects, dependent: :destroy
has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event"
+ has_many :recent_events, foreign_key: :author_id, class_name: "Event", order: "id DESC"
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
- has_many :groups, class_name: "Group", foreign_key: :owner_id
- has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
-
- has_many :projects, through: :users_projects
-
- has_many :user_team_user_relationships, dependent: :destroy
-
- has_many :user_teams, through: :user_team_user_relationships
- has_many :user_team_project_relationships, through: :user_teams
- has_many :team_projects, through: :user_team_project_relationships
+ has_many :groups_projects, through: :groups, source: :projects
+ has_many :personal_projects, through: :namespace, source: :projects
+ has_many :projects, through: :users_projects
+ has_many :own_projects, foreign_key: :creator_id, class_name: 'Project'
+ has_many :owned_projects, through: :namespaces, source: :projects
+ #
+ # Validations
+ #
validates :name, presence: true
+ validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ }
validates :bio, length: { within: 0..255 }
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,
+ exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.username_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
+ validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? }
before_validation :generate_password, on: :create
+ before_validation :sanitize_attrs
+
before_save :ensure_authentication_token
+
alias_attribute :private_token, :authentication_token
delegate :path, to: :namespace, allow_nil: true, prefix: true
+ state_machine :state, initial: :active do
+ after_transition any => :blocked do |user, transition|
+ # Remove user from all projects and
+ user.users_projects.find_each do |membership|
+ # skip owned resources
+ next if membership.project.owner == user
+
+ return false unless membership.destroy
+ end
+
+ # Remove user from all groups
+ user.users_groups.find_each do |membership|
+ # skip owned resources
+ next if membership.group.last_owner?(user)
+
+ return false unless membership.destroy
+ end
+ end
+
+ event :block do
+ transition active: :blocked
+ end
+
+ event :activate do
+ transition blocked: :active
+ end
+ end
+
# Scopes
scope :admins, -> { where(admin: true) }
- scope :blocked, -> { where(blocked: true) }
- scope :active, -> { where(blocked: false) }
+ scope :blocked, -> { with_state(:blocked) }
+ scope :active, -> { with_state(:active) }
scope :alphabetically, -> { order('name ASC') }
scope :in_team, ->(team){ where(id: team.member_ids) }
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
+ scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : scoped }
+ scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') }
+ scope :ldap, -> { where(provider: 'ldap') }
+
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
#
# Class methods
#
class << self
+ # Devise method overridden to allow sing in with email or username
+ def find_for_database_authentication(warden_conditions)
+ conditions = warden_conditions.dup
+ if login = conditions.delete(:login)
+ where(conditions).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first
+ else
+ where(conditions).first
+ end
+ end
+
def filter filter_name
case filter_name
when "admins"; self.admins
@@ -109,36 +187,32 @@ class User < ActiveRecord::Base
end
end
- def not_in_project(project)
- if project.users.present?
- where("id not in (:ids)", ids: project.users.map(&:id) )
- else
- scoped
- end
- end
-
- def without_projects
- where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)')
- end
-
- def create_from_omniauth(auth, ldap = false)
- gitlab_auth.create_from_omniauth(auth, ldap)
- end
-
- def find_or_new_for_omniauth(auth)
- gitlab_auth.find_or_new_for_omniauth(auth)
+ def search query
+ where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%")
end
- def find_for_ldap_auth(auth, signed_in_resource = nil)
- gitlab_auth.find_for_ldap_auth(auth, signed_in_resource)
+ def by_username_or_id(name_or_id)
+ if (name_or_id.is_a?(Integer))
+ User.find_by_id(name_or_id)
+ else
+ User.find_by_username(name_or_id)
+ end
end
- def gitlab_auth
- Gitlab::Auth.new
+ def build_user(attrs = {}, options= {})
+ if options[:as] == :admin
+ User.new(defaults.merge(attrs.symbolize_keys), options)
+ else
+ User.new(attrs, options).with_defaults
+ end
end
- def search query
- where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%")
+ def defaults
+ {
+ projects_limit: Gitlab.config.gitlab.default_projects_limit,
+ can_create_group: Gitlab.config.gitlab.default_can_create_group,
+ theme_id: Gitlab.config.gitlab.default_theme
+ }
end
end
@@ -150,6 +224,10 @@ class User < ActiveRecord::Base
username
end
+ def notification
+ @notification ||= Notification.new(self)
+ end
+
def generate_password
if self.force_random_password
self.password = self.password_confirmation = Devise.friendly_token.first(8)
@@ -163,67 +241,26 @@ class User < ActiveRecord::Base
end
end
- # Namespaces user has access to
- def namespaces
- namespaces = []
-
- # Add user account namespace
- namespaces << self.namespace if self.namespace
-
- # Add groups you can manage
- namespaces += groups.all
-
- namespaces
- end
-
- # Groups where user is an owner
- def owned_groups
- groups
- end
-
# Groups user has access to
def authorized_groups
@authorized_groups ||= begin
- groups = Group.where(id: self.authorized_projects.pluck(:namespace_id)).all
- groups = groups + self.groups
- groups.uniq
- end
+ group_ids = (groups.pluck(:id) + own_groups.pluck(:id) + authorized_projects.pluck(:namespace_id))
+ Group.where(id: group_ids).order('namespaces.name ASC')
+ end
end
# Projects user has access to
def authorized_projects
- project_ids = users_projects.pluck(:project_id)
- project_ids = project_ids | owned_projects.pluck(:id)
- Project.where(id: project_ids)
- end
-
- # Projects in user namespace
- def personal_projects
- Project.personal(self)
- end
-
- # Projects where user is an owner
- def owned_projects
- Project.where("(projects.namespace_id IN (:namespaces)) OR
- (projects.namespace_id IS NULL AND projects.creator_id = :user_id)",
- namespaces: namespaces.map(&:id), user_id: self.id)
+ @authorized_projects ||= begin
+ project_ids = (owned_projects.pluck(:id) + groups_projects.pluck(:id) + projects.pluck(:id)).uniq
+ Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC')
+ end
end
# Team membership in authorized projects
def tm_in_authorized_projects
- UsersProject.where(project_id: authorized_projects.map(&:id), user_id: self.id)
- end
-
- # Returns a string for use as a Gitolite user identifier
- #
- # Note that Gitolite 2.x requires the following pattern for users:
- #
- # ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$
- def identifier
- # Replace non-word chars with underscores, then make sure it starts with
- # valid chars
- email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '')
+ UsersProject.where(project_id: authorized_projects.map(&:id), user_id: self.id)
end
def is_admin?
@@ -234,8 +271,12 @@ class User < ActiveRecord::Base
keys.count == 0
end
+ def can_change_username?
+ Gitlab.config.gitlab.username_changing_enabled
+ end
+
def can_create_project?
- projects_limit > personal_projects.count
+ projects_limit_left > 0
end
def can_create_group?
@@ -263,18 +304,11 @@ class User < ActiveRecord::Base
end
def cared_merge_requests
- MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id)
+ MergeRequest.cared(self)
end
- # Remove user from all projects and
- # set blocked attribute to true
- def block
- users_projects.find_each do |membership|
- return false unless membership.destroy
- end
-
- self.blocked = true
- save
+ def projects_limit_left
+ projects_limit - personal_projects.count
end
def projects_limit_percent
@@ -296,25 +330,65 @@ class User < ActiveRecord::Base
end
def several_namespaces?
- namespaces.size > 1
+ namespaces.many? || owned_groups.any?
end
def namespace_id
namespace.try :id
end
- def authorized_teams
- @authorized_teams ||= begin
- ids = []
- ids << UserTeam.with_member(self).pluck('user_teams.id')
- ids << UserTeam.created_by(self).pluck('user_teams.id')
- ids.flatten
+ def name_with_username
+ "#{name} (#{username})"
+ end
+
+ def tm_of(project)
+ project.team_member_by_id(self.id)
+ end
+
+ def already_forked? project
+ !!fork_of(project)
+ end
+
+ def fork_of project
+ links = ForkedProjectLink.where(forked_from_project_id: project, forked_to_project_id: personal_projects)
+
+ if links.any?
+ links.first.forked_to_project
+ else
+ nil
+ end
+ end
- UserTeam.where(id: ids)
- end
+ def ldap_user?
+ extern_uid && provider == 'ldap'
end
- def owned_teams
- UserTeam.where(owner_id: self.id)
+ def accessible_deploy_keys
+ DeployKey.in_projects(self.authorized_projects).uniq
+ end
+
+ def created_by
+ User.find_by_id(created_by_id) if created_by_id
+ end
+
+ def sanitize_attrs
+ %w(name username skype linkedin twitter bio).each do |attr|
+ value = self.send(attr)
+ self.send("#{attr}=", Sanitize.clean(value)) if value.present?
+ end
+ end
+
+ def solo_owned_groups
+ @solo_owned_groups ||= owned_groups.select do |group|
+ group.owners == [self]
+ end
+ end
+
+ def with_defaults
+ User.defaults.each do |k, v|
+ self.send("#{k}=", v)
+ end
+
+ self
end
end
diff --git a/app/models/user_team.rb b/app/models/user_team.rb
deleted file mode 100644
index dc8cf9eeb22..00000000000
--- a/app/models/user_team.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-# == Schema Information
-#
-# Table name: user_teams
-#
-# id :integer not null, primary key
-# name :string(255)
-# path :string(255)
-# owner_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
-class UserTeam < ActiveRecord::Base
- attr_accessible :name, :owner_id, :path
-
- belongs_to :owner, class_name: User
-
- has_many :user_team_project_relationships, dependent: :destroy
- has_many :user_team_user_relationships, dependent: :destroy
-
- has_many :projects, through: :user_team_project_relationships
- has_many :members, through: :user_team_user_relationships, source: :user
-
- validates :name, presence: true, uniqueness: true
- validates :owner, presence: true
- validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
- format: { with: Gitlab::Regex.path_regex,
- message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
-
- scope :with_member, ->(user){ joins(:user_team_user_relationships).where(user_team_user_relationships: {user_id: user.id}) }
- scope :with_project, ->(project){ joins(:user_team_project_relationships).where(user_team_project_relationships: {project_id: project})}
- scope :without_project, ->(project){ where("user_teams.id NOT IN (:ids)", ids: (a = with_project(project); a.blank? ? 0 : a))}
- scope :created_by, ->(user){ where(owner_id: user) }
-
- class << self
- def search query
- where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
- end
-
- def global_id
- 'GLN'
- end
-
- def access_roles
- UsersProject.access_roles
- end
- end
-
- def to_param
- path
- end
-
- def assign_to_projects(projects, access)
- projects.each do |project|
- assign_to_project(project, access)
- end
- end
-
- def assign_to_project(project, access)
- Gitlab::UserTeamManager.assign(self, project, access)
- end
-
- def resign_from_project(project)
- Gitlab::UserTeamManager.resign(self, project)
- end
-
- def add_members(users, access, group_admin)
- users.each do |user|
- add_member(user, access, group_admin)
- end
- end
-
- def add_member(user, access, group_admin)
- Gitlab::UserTeamManager.add_member_into_team(self, user, access, group_admin)
- end
-
- def remove_member(user)
- Gitlab::UserTeamManager.remove_member_from_team(self, user)
- end
-
- def update_membership(user, options)
- Gitlab::UserTeamManager.update_team_user_membership(self, user, options)
- end
-
- def update_project_access(project, permission)
- Gitlab::UserTeamManager.update_project_greates_access(self, project, permission)
- end
-
- def max_project_access(project)
- user_team_project_relationships.find_by_project_id(project).greatest_access
- end
-
- def human_max_project_access(project)
- self.class.access_roles.invert[max_project_access(project)]
- end
-
- def default_projects_access(member)
- user_team_user_relationships.find_by_user_id(member).permission
- end
-
- def human_default_projects_access(member)
- self.class.access_roles.invert[default_projects_access(member)]
- end
-
- def admin?(member)
- user_team_user_relationships.with_user(member).first.group_admin?
- end
-
-end
diff --git a/app/models/user_team_project_relationship.rb b/app/models/user_team_project_relationship.rb
deleted file mode 100644
index a7aa88970c7..00000000000
--- a/app/models/user_team_project_relationship.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# == Schema Information
-#
-# Table name: user_team_project_relationships
-#
-# id :integer not null, primary key
-# project_id :integer
-# user_team_id :integer
-# greatest_access :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
-class UserTeamProjectRelationship < ActiveRecord::Base
- attr_accessible :greatest_access, :project_id, :user_team_id
-
- belongs_to :user_team
- belongs_to :project
-
- validates :project, presence: true
- validates :user_team, presence: true
- validate :check_greatest_access
-
- scope :with_project, ->(project){ where(project_id: project.id) }
-
- def team_name
- user_team.name
- end
-
- private
-
- def check_greatest_access
- errors.add(:base, :incorrect_access_code) unless correct_access?
- end
-
- def correct_access?
- return false if greatest_access.blank?
- return true if UsersProject.access_roles.has_value?(greatest_access)
- false
- end
-end
diff --git a/app/models/user_team_user_relationship.rb b/app/models/user_team_user_relationship.rb
deleted file mode 100644
index 1f7e2625f5f..00000000000
--- a/app/models/user_team_user_relationship.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# == Schema Information
-#
-# Table name: user_team_user_relationships
-#
-# id :integer not null, primary key
-# user_id :integer
-# user_team_id :integer
-# group_admin :boolean
-# permission :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
-class UserTeamUserRelationship < ActiveRecord::Base
- attr_accessible :group_admin, :permission, :user_id, :user_team_id
-
- belongs_to :user_team
- belongs_to :user
-
- validates :user_team, presence: true
- validates :user, presence: true
-
- scope :with_user, ->(user) { where(user_id: user.id) }
-
- def user_name
- user.name
- end
-
- def access_human
- UsersProject.access_roles.invert[permission]
- end
-end
diff --git a/app/models/users_group.rb b/app/models/users_group.rb
new file mode 100644
index 00000000000..181bf322283
--- /dev/null
+++ b/app/models/users_group.rb
@@ -0,0 +1,46 @@
+# == Schema Information
+#
+# Table name: users_groups
+#
+# id :integer not null, primary key
+# group_access :integer not null
+# group_id :integer not null
+# user_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# notification_level :integer default(3), not null
+#
+
+class UsersGroup < ActiveRecord::Base
+ include Notifiable
+ include Gitlab::Access
+
+ def self.group_access_roles
+ Gitlab::Access.options_with_owner
+ end
+
+ attr_accessible :group_access, :user_id
+
+ belongs_to :user
+ belongs_to :group
+
+ scope :guests, -> { where(group_access: GUEST) }
+ scope :reporters, -> { where(group_access: REPORTER) }
+ scope :developers, -> { where(group_access: DEVELOPER) }
+ scope :masters, -> { where(group_access: MASTER) }
+ scope :owners, -> { where(group_access: OWNER) }
+
+ scope :with_group, ->(group) { where(group_id: group.id) }
+ scope :with_user, ->(user) { where(user_id: user.id) }
+
+ validates :group_access, inclusion: { in: UsersGroup.group_access_roles.values }, presence: true
+ validates :user_id, presence: true
+ validates :group_id, presence: true
+ validates :user_id, uniqueness: { scope: [:group_id], message: "already exists in group" }
+
+ delegate :name, :username, :email, to: :user, prefix: true
+
+ def access_field
+ group_access
+ end
+end
diff --git a/app/models/users_project.rb b/app/models/users_project.rb
index 486aaa6966a..6f147859a5c 100644
--- a/app/models/users_project.rb
+++ b/app/models/users_project.rb
@@ -2,32 +2,28 @@
#
# Table name: users_projects
#
-# id :integer not null, primary key
-# user_id :integer not null
-# project_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# project_access :integer default(0), not null
+# id :integer not null, primary key
+# user_id :integer not null
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# project_access :integer default(0), not null
+# notification_level :integer default(3), not null
#
class UsersProject < ActiveRecord::Base
- include Gitolited
-
- GUEST = 10
- REPORTER = 20
- DEVELOPER = 30
- MASTER = 40
+ include Gitlab::ShellAdapter
+ include Notifiable
+ include Gitlab::Access
attr_accessible :user, :user_id, :project_access
belongs_to :user
belongs_to :project
- attr_accessor :skip_git
-
validates :user, presence: true
validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
- validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true
+ validates :project_access, inclusion: { in: Gitlab::Access.values }, presence: true
validates :project, presence: true
delegate :name, :username, :email, to: :user, prefix: true
@@ -38,7 +34,7 @@ class UsersProject < ActiveRecord::Base
scope :masters, -> { where(project_access: MASTER) }
scope :in_project, ->(project) { where(project_id: project.id) }
- scope :in_projects, ->(projects) { where(project_id: project_ids) }
+ scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) }
scope :with_user, ->(user) { where(user_id: user.id) }
class << self
@@ -75,7 +71,6 @@ class UsersProject < ActiveRecord::Base
user_ids.each do |user_id|
users_project = UsersProject.new(project_access: project_access, user_id: user_id)
users_project.project_id = project_id
- users_project.skip_git = true
users_project.save
end
end
@@ -90,7 +85,6 @@ class UsersProject < ActiveRecord::Base
UsersProject.transaction do
users_projects = UsersProject.where(project_id: project_ids)
users_projects.each do |users_project|
- users_project.skip_git = true
users_project.destroy
end
end
@@ -105,33 +99,19 @@ class UsersProject < ActiveRecord::Base
end
def roles_hash
- {
- guest: GUEST,
- reporter: REPORTER,
- developer: DEVELOPER,
- master: MASTER
- }
+ Gitlab::Access.sym_options
end
def access_roles
- {
- "Guest" => GUEST,
- "Reporter" => REPORTER,
- "Developer" => DEVELOPER,
- "Master" => MASTER
- }
+ Gitlab::Access.options
end
end
- def project_access_human
- Project.access_options.key(self.project_access)
- end
-
- def repo_access_human
- self.class.access_roles.invert[self.project_access]
+ def access_field
+ project_access
end
- def skip_git?
- !!@skip_git
+ def owner?
+ project.owner == user
end
end
diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb
index efa27f31982..3f22b1082fb 100644
--- a/app/models/web_hook.rb
+++ b/app/models/web_hook.rb
@@ -28,10 +28,14 @@ class WebHook < ActiveRecord::Base
WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" })
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
+ auth = {
+ username: URI.decode(parsed_url.user),
+ password: URI.decode(parsed_url.password),
+ }
WebHook.post(post_url,
body: data.to_json,
headers: {"Content-Type" => "application/json"},
- basic_auth: {username: parsed_url.user, password: parsed_url.password})
+ basic_auth: auth)
end
end
diff --git a/app/models/wiki.rb b/app/models/wiki.rb
deleted file mode 100644
index 7f488ca7625..00000000000
--- a/app/models/wiki.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# == Schema Information
-#
-# Table name: wikis
-#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# project_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-# slug :string(255)
-# user_id :integer
-#
-
-class Wiki < ActiveRecord::Base
- attr_accessible :title, :content, :slug
-
- belongs_to :project
- belongs_to :user
- has_many :notes, as: :noteable, dependent: :destroy
-
- validates :content, presence: true
- validates :user, presence: true
- validates :title, presence: true, length: 1..250
-
- before_update :set_slug
-
- scope :ordered, order("created_at DESC")
-
- def to_param
- slug
- end
-
- class << self
- def search(query)
- where("title like :query OR content like :query", query: "%#{query}%")
- end
- end
-
- protected
-
- def self.regenerate_from wiki
- regenerated_field = [:slug, :content, :title]
-
- new_wiki = Wiki.new
- regenerated_field.each do |field|
- new_wiki.send("#{field}=", wiki.send(field))
- end
- new_wiki
- end
-
- def set_slug
- self.slug = self.title.parameterize
- end
-end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
new file mode 100644
index 00000000000..a7e6fea5ad0
--- /dev/null
+++ b/app/models/wiki_page.rb
@@ -0,0 +1,185 @@
+class WikiPage
+ include ActiveModel::Validations
+ include ActiveModel::Conversion
+ include StaticModel
+ extend ActiveModel::Naming
+
+ def self.primary_key
+ 'slug'
+ end
+
+ def self.model_name
+ ActiveModel::Name.new(self, nil, 'wiki')
+ end
+
+ def to_key
+ [:slug]
+ end
+
+ validates :title, presence: true
+ validates :content, presence: true
+
+ # The Gitlab GollumWiki instance.
+ attr_reader :wiki
+
+ # The raw Gollum::Page instance.
+ attr_reader :page
+
+ # The attributes Hash used for storing and validating
+ # new Page values before writing to the Gollum repository.
+ attr_accessor :attributes
+
+ def initialize(wiki, page = nil, persisted = false)
+ @wiki = wiki
+ @page = page
+ @persisted = persisted
+ @attributes = {}.with_indifferent_access
+
+ set_attributes if persisted?
+ end
+
+ # The escaped URL path of this page.
+ def slug
+ @attributes[:slug]
+ end
+
+ alias :to_param :slug
+
+ # The formatted title of this page.
+ def title
+ @attributes[:title] || ""
+ end
+
+ # Sets the title of this page.
+ def title=(new_title)
+ @attributes[:title] = new_title
+ end
+
+ # The raw content of this page.
+ def content
+ @attributes[:content]
+ end
+
+ # The processed/formatted content of this page.
+ def formatted_content
+ @attributes[:formatted_content]
+ end
+
+ # The markup format for the page.
+ def format
+ @attributes[:format] || :markdown
+ end
+
+ # The commit message for this page version.
+ def message
+ version.try(:message)
+ end
+
+ # The Gitlab Commit instance for this page.
+ def version
+ return nil unless persisted?
+
+ @version ||= Commit.new(Gitlab::Git::Commit.new(@page.version))
+ end
+
+ # Returns an array of Gitlab Commit instances.
+ def versions
+ return [] unless persisted?
+
+ @page.versions.map { |v| Commit.new(Gitlab::Git::Commit.new(v)) }
+ end
+
+ def commit
+ versions.first
+ end
+
+ # Returns the Date that this latest version was
+ # created on.
+ def created_at
+ @page.version.date
+ end
+
+ # Returns boolean True or False if this instance
+ # is an old version of the page.
+ def historical?
+ @page.historical?
+ end
+
+ # Returns boolean True or False if this instance
+ # has been fully saved to disk or not.
+ def persisted?
+ @persisted == true
+ end
+
+ # Creates a new Wiki Page.
+ #
+ # attr - Hash of attributes to set on the new page.
+ # :title - The title for the new page.
+ # :content - The raw markup content.
+ # :format - Optional symbol representing the
+ # content format. Can be any type
+ # listed in the GollumWiki::MARKUPS
+ # Hash.
+ # :message - Optional commit message to set on
+ # the new page.
+ #
+ # Returns the String SHA1 of the newly created page
+ # or False if the save was unsuccessful.
+ def create(attr = {})
+ @attributes.merge!(attr)
+
+ save :create_page, title, content, format, message
+ end
+
+ # Updates an existing Wiki Page, creating a new version.
+ #
+ # new_content - The raw markup content to replace the existing.
+ # format - Optional symbol representing the content format.
+ # See GollumWiki::MARKUPS Hash for available formats.
+ # message - Optional commit message to set on the new version.
+ #
+ # Returns the String SHA1 of the newly created page
+ # or False if the save was unsuccessful.
+ def update(new_content = "", format = :markdown, message = nil)
+ @attributes[:content] = new_content
+ @attributes[:format] = format
+
+ save :update_page, @page, content, format, message
+ end
+
+ # Destroys the Wiki Page.
+ #
+ # Returns boolean True or False.
+ def delete
+ if wiki.delete_page(@page)
+ true
+ else
+ false
+ end
+ end
+
+ private
+
+ def set_attributes
+ attributes[:slug] = @page.escaped_url_path
+ attributes[:title] = @page.title
+ attributes[:content] = @page.raw_data
+ attributes[:formatted_content] = @page.formatted_data
+ attributes[:format] = @page.format
+ end
+
+ def save(method, *args)
+ if valid? && wiki.send(method, *args)
+ @page = wiki.wiki.paged(title)
+
+ set_attributes
+
+ @persisted = true
+ else
+ errors.add(:base, wiki.error_message) if wiki.error_message
+ @persisted = false
+ end
+ @persisted
+ end
+
+end
diff --git a/app/observers/activity_observer.rb b/app/observers/activity_observer.rb
index b568bb6b763..d754ac8185a 100644
--- a/app/observers/activity_observer.rb
+++ b/app/observers/activity_observer.rb
@@ -1,34 +1,39 @@
-class ActivityObserver < ActiveRecord::Observer
- observe :issue, :merge_request, :note, :milestone
+class ActivityObserver < BaseObserver
+ observe :issue, :note, :milestone
def after_create(record)
event_author_id = record.author_id
- # Skip status notes
- if record.kind_of?(Note) && record.note.include?("_Status changed to ")
- return true
+ 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
- Event.create(
- project: record.project,
- target_id: record.id,
- target_type: record.class.name,
- action: Event.determine_action(record),
- author_id: event_author_id
- )
+ create_event(record, Event.determine_action(record))
end
end
- def after_save(record)
- if record.changed.include?("closed") && record.author_id_of_changes
- Event.create(
- project: record.project,
- target_id: record.id,
- target_type: record.class.name,
- action: (record.closed ? Event::CLOSED : Event::REOPENED),
- author_id: record.author_id_of_changes
- )
- 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
new file mode 100644
index 00000000000..f9a0242ce77
--- /dev/null
+++ b/app/observers/base_observer.rb
@@ -0,0 +1,17 @@
+class BaseObserver < ActiveRecord::Observer
+ def notification
+ NotificationService.new
+ end
+
+ def log_info message
+ Gitlab::AppLogger.info message
+ end
+
+ def current_user
+ Thread.current[:current_user]
+ end
+
+ def current_commit
+ Thread.current[:current_commit]
+ end
+end
diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb
index 262d0f892c4..886d8b776fb 100644
--- a/app/observers/issue_observer.rb
+++ b/app/observers/issue_observer.rb
@@ -1,33 +1,32 @@
-class IssueObserver < ActiveRecord::Observer
- cattr_accessor :current_user
-
+class IssueObserver < BaseObserver
def after_create(issue)
- if issue.assignee && issue.assignee != current_user
- Notify.delay.new_issue_email(issue.id)
- end
+ notification.new_issue(issue, current_user)
+
+ issue.create_cross_references!(issue.project, current_user)
+ end
+
+ def after_close(issue, transition)
+ notification.close_issue(issue, current_user)
+
+ create_note(issue)
+ end
+
+ def after_reopen(issue, transition)
+ create_note(issue)
end
def after_update(issue)
- send_reassigned_email(issue) if issue.is_being_reassigned?
-
- status = nil
- status = 'closed' if issue.is_being_closed?
- status = 'reopened' if issue.is_being_reopened?
- if status
- Note.create_status_change_note(issue, current_user, status)
- [issue.author, issue.assignee].compact.each do |recipient|
- Notify.delay.issue_status_changed_email(recipient.id, issue.id, status, current_user.id)
- end
+ if issue.is_being_reassigned?
+ notification.reassigned_issue(issue, current_user)
end
+
+ issue.notice_added_references(issue.project, current_user)
end
protected
- def send_reassigned_email(issue)
- recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id }
-
- recipient_ids.each do |recipient_id|
- Notify.delay.reassigned_issue_email(recipient_id, issue.id, issue.assignee_id_was)
- end
+ # Create issue note with service comment like 'Status changed to closed'
+ def create_note(issue)
+ Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
end
end
diff --git a/app/observers/key_observer.rb b/app/observers/key_observer.rb
index 664cbdfd234..c013594e787 100644
--- a/app/observers/key_observer.rb
+++ b/app/observers/key_observer.rb
@@ -1,12 +1,12 @@
-class KeyObserver < ActiveRecord::Observer
- include Gitolited
-
- def after_save(key)
+class KeyObserver < BaseObserver
+ def after_create(key)
GitlabShellWorker.perform_async(
:add_key,
key.shell_id,
key.key
)
+
+ notification.new_key(key)
end
def after_destroy(key)
diff --git a/app/observers/merge_request_observer.rb b/app/observers/merge_request_observer.rb
index 6d3c2bdd186..d70da514cd2 100644
--- a/app/observers/merge_request_observer.rb
+++ b/app/observers/merge_request_observer.rb
@@ -1,31 +1,56 @@
-class MergeRequestObserver < ActiveRecord::Observer
- cattr_accessor :current_user
+class MergeRequestObserver < ActivityObserver
+ observe :merge_request
def after_create(merge_request)
- if merge_request.assignee && merge_request.assignee != current_user
- Notify.delay.new_merge_request_email(merge_request.id)
+ if merge_request.author_id
+ create_event(merge_request, Event.determine_action(merge_request))
end
+
+ notification.new_merge_request(merge_request, current_user)
+
+ merge_request.create_cross_references!(merge_request.project, current_user)
end
- def after_update(merge_request)
- send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
+ def after_close(merge_request, transition)
+ create_event(merge_request, Event::CLOSED)
+ Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
- status = nil
- status = 'closed' if merge_request.is_being_closed?
- status = 'reopened' if merge_request.is_being_reopened?
- if status
- Note.create_status_change_note(merge_request, current_user, status)
- end
+ notification.close_mr(merge_request, current_user)
end
- protected
+ def after_merge(merge_request, transition)
+ notification.merge_mr(merge_request)
+ # Since MR can be merged via sidekiq
+ # to prevent event duplication do this check
+ return true if merge_request.merge_event
- def send_reassigned_email(merge_request)
- recipients_ids = merge_request.assignee_id_was, merge_request.assignee_id
- recipients_ids.delete current_user.id
+ 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
+ )
+ end
- recipients_ids.each do |recipient_id|
- Notify.delay.reassigned_merge_request_email(recipient_id, merge_request.id, merge_request.assignee_id_was)
- end
+ def after_reopen(merge_request, transition)
+ create_event(merge_request, Event::REOPENED)
+ Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
+ end
+
+ def after_update(merge_request)
+ notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
+
+ merge_request.notice_added_references(merge_request.project, current_user)
+ 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
end
diff --git a/app/observers/note_observer.rb b/app/observers/note_observer.rb
index 4ee9fadf4da..d31b6e8d7ce 100644
--- a/app/observers/note_observer.rb
+++ b/app/observers/note_observer.rb
@@ -1,39 +1,17 @@
-class NoteObserver < ActiveRecord::Observer
-
+class NoteObserver < BaseObserver
def after_create(note)
- send_notify_mails(note)
- end
-
- protected
-
- def send_notify_mails(note)
- if note.notify
- notify_team(note)
- elsif note.notify_author
- # Notify only author of resource
- if note.commit_author
- Notify.delay.note_commit_email(note.commit_author.id, note.id)
- end
- else
- # Otherwise ignore it
- nil
- end
- end
-
- # Notifies the whole team except the author of note
- def notify_team(note)
- # Note: wall posts are not "attached" to anything, so fall back to "Wall"
- noteable_type = note.noteable_type.presence || "Wall"
- notify_method = "note_#{noteable_type.underscore}_email".to_sym
+ notification.new_note(note)
- if Notify.respond_to? notify_method
- team_without_note_author(note).map do |u|
- Notify.delay.send(notify_method, u.id, note.id)
+ unless note.system?
+ # Create a cross-reference note if this Note contains GFM that names an
+ # issue, merge request, or commit.
+ note.references.each do |mentioned|
+ Note.create_cross_reference_note(mentioned, note.noteable, note.author, note.project)
end
end
end
- def team_without_note_author(note)
- note.project.users.reject { |u| u.id == note.author.id }
+ def after_update(note)
+ note.notice_added_references(note.project, current_user)
end
end
diff --git a/app/observers/project_activity_cache_observer.rb b/app/observers/project_activity_cache_observer.rb
new file mode 100644
index 00000000000..96ced90db80
--- /dev/null
+++ b/app/observers/project_activity_cache_observer.rb
@@ -0,0 +1,8 @@
+class ProjectActivityCacheObserver < BaseObserver
+ observe :event
+
+ def after_create(event)
+ event.project.update_column(:last_activity_at, event.created_at) if event.project
+ end
+end
+
diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb
index 4b1f8295dc2..acbb719b69a 100644
--- a/app/observers/project_observer.rb
+++ b/app/observers/project_observer.rb
@@ -1,15 +1,34 @@
-class ProjectObserver < ActiveRecord::Observer
+class ProjectObserver < BaseObserver
def after_create(project)
- GitlabShellWorker.perform_async(
- :add_repository,
- project.path_with_namespace
- )
+ project.update_column(:last_activity_at, project.created_at)
+
+ return true if project.forked?
- log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
+ 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
end
def after_update(project)
project.send_move_instructions if project.namespace_id_changed?
+ project.rename_repo if project.path_changed?
+
+ GitlabShellWorker.perform_async(
+ :update_repository_head,
+ project.path_with_namespace,
+ project.default_branch
+ ) if project.default_branch_changed?
+ end
+
+ def before_destroy(project)
+ project.repository.expire_cache unless project.empty_repo?
end
def after_destroy(project)
@@ -18,14 +37,13 @@ class ProjectObserver < ActiveRecord::Observer
project.path_with_namespace
)
+ GitlabShellWorker.perform_async(
+ :remove_repository,
+ project.path_with_namespace + ".wiki"
+ )
+
project.satellite.destroy
log_info("Project \"#{project.name}\" was removed")
end
-
- protected
-
- def log_info message
- Gitlab::AppLogger.info message
- end
end
diff --git a/app/observers/system_hook_observer.rb b/app/observers/system_hook_observer.rb
index 312cd2b3622..3a649fd590d 100644
--- a/app/observers/system_hook_observer.rb
+++ b/app/observers/system_hook_observer.rb
@@ -1,67 +1,11 @@
-class SystemHookObserver < ActiveRecord::Observer
+class SystemHookObserver < BaseObserver
observe :user, :project, :users_project
-
- def after_create(model)
- if model.kind_of? Project
- SystemHook.all_hooks_fire({
- event_name: "project_create",
- name: model.name,
- path: model.path,
- project_id: model.id,
- owner_name: model.owner.name,
- owner_email: model.owner.email,
- created_at: model.created_at
- })
- elsif model.kind_of? User
- SystemHook.all_hooks_fire({
- event_name: "user_create",
- name: model.name,
- email: model.email,
- created_at: model.created_at
- })
-
- elsif model.kind_of? UsersProject
- SystemHook.all_hooks_fire({
- event_name: "user_add_to_team",
- project_name: model.project.name,
- project_path: model.project.path,
- project_id: model.project_id,
- user_name: model.user.name,
- user_email: model.user.email,
- project_access: model.repo_access_human,
- created_at: model.created_at
- })
- end
+ def after_create(model)
+ SystemHooksService.execute_hooks_for(model, :create)
end
def after_destroy(model)
- if model.kind_of? Project
- SystemHook.all_hooks_fire({
- event_name: "project_destroy",
- name: model.name,
- path: model.path,
- project_id: model.id,
- owner_name: model.owner.name,
- owner_email: model.owner.email,
- })
- elsif model.kind_of? User
- SystemHook.all_hooks_fire({
- event_name: "user_destroy",
- name: model.name,
- email: model.email
- })
-
- elsif model.kind_of? UsersProject
- SystemHook.all_hooks_fire({
- event_name: "user_remove_from_team",
- project_name: model.project.name,
- project_path: model.project.path,
- project_id: model.project_id,
- user_name: model.user.name,
- user_email: model.user.email,
- project_access: model.repo_access_human
- })
- end
+ SystemHooksService.execute_hooks_for(model, :destroy)
end
end
diff --git a/app/observers/user_observer.rb b/app/observers/user_observer.rb
index c1179ed7881..fba0f1006d9 100644
--- a/app/observers/user_observer.rb
+++ b/app/observers/user_observer.rb
@@ -1,8 +1,8 @@
-class UserObserver < ActiveRecord::Observer
+class UserObserver < BaseObserver
def after_create(user)
log_info("User \"#{user.name}\" (#{user.email}) was created")
- Notify.delay.new_user_email(user.id, user.password)
+ notification.new_user(user)
end
def after_destroy user
@@ -10,18 +10,11 @@ class UserObserver < ActiveRecord::Observer
end
def after_save user
+ # Ensure user has namespace
+ user.create_namespace!(path: user.username, name: user.username) unless user.namespace
+
if user.username_changed?
- if user.namespace
- user.namespace.update_attributes(path: user.username)
- else
- user.create_namespace!(path: user.username, name: user.username)
- end
+ user.namespace.update_attributes(path: user.username, name: user.username)
end
end
-
- protected
-
- def log_info message
- Gitlab::AppLogger.info message
- end
end
diff --git a/app/observers/users_group_observer.rb b/app/observers/users_group_observer.rb
new file mode 100644
index 00000000000..ecdbede89d9
--- /dev/null
+++ b/app/observers/users_group_observer.rb
@@ -0,0 +1,9 @@
+class UsersGroupObserver < BaseObserver
+ def after_create(membership)
+ notification.new_group_member(membership)
+ end
+
+ def after_update(membership)
+ notification.update_group_member(membership)
+ end
+end
diff --git a/app/observers/users_project_observer.rb b/app/observers/users_project_observer.rb
index 66b421753f0..ca9649c76ab 100644
--- a/app/observers/users_project_observer.rb
+++ b/app/observers/users_project_observer.rb
@@ -1,7 +1,6 @@
-class UsersProjectObserver < ActiveRecord::Observer
+class UsersProjectObserver < BaseObserver
def after_commit(users_project)
return if users_project.destroyed?
- Notify.delay.project_access_granted_email(users_project.id)
end
def after_create(users_project)
@@ -10,6 +9,12 @@ class UsersProjectObserver < ActiveRecord::Observer
action: Event::JOINED,
author_id: users_project.user.id
)
+
+ notification.new_team_member(users_project)
+ end
+
+ def after_update(users_project)
+ notification.update_team_member(users_project)
end
def after_destroy(users_project)
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
new file mode 100644
index 00000000000..f9d43e60de6
--- /dev/null
+++ b/app/services/git_push_service.rb
@@ -0,0 +1,194 @@
+class GitPushService
+ attr_accessor :project, :user, :push_data, :push_commits
+
+ # This method will be called after each git update
+ # and only if the provided user and project is present in GitLab.
+ #
+ # All callbacks for post receive action should be placed here.
+ #
+ # Next, this method:
+ # 1. Creates the push event
+ # 2. Ensures that the project satellite exists
+ # 3. Updates merge requests
+ # 4. Recognizes cross-references from commit messages
+ # 5. Executes the project's web hooks
+ # 6. Executes the project's services
+ #
+ def execute(project, user, oldrev, newrev, ref)
+ @project, @user = project, user
+
+ # Collect data for this git push
+ @push_commits = project.repository.commits_between(oldrev, newrev)
+ @push_data = post_receive_data(oldrev, newrev, ref)
+
+ create_push_event
+
+ project.ensure_satellite_exists
+ project.discover_default_branch
+ project.repository.expire_cache
+
+ if push_to_existing_branch?(ref, oldrev)
+ project.update_merge_requests(oldrev, newrev, ref, @user)
+ process_commit_messages(ref)
+ end
+
+ if push_to_branch?(ref)
+ project.execute_hooks(@push_data.dup)
+ project.execute_services(@push_data.dup)
+ end
+
+ if push_to_new_branch?(ref, oldrev)
+ # Re-find the pushed commits.
+ if is_default_branch?(ref)
+ # Initial push to the default branch. Take the full history of that branch as "newly pushed".
+ @push_commits = project.repository.commits(newrev)
+ else
+ # Use the pushed commits that aren't reachable by the default branch
+ # as a heuristic. This may include more commits than are actually pushed, but
+ # that shouldn't matter because we check for existing cross-references later.
+ @push_commits = project.repository.commits_between(project.default_branch, newrev)
+ end
+
+ process_commit_messages(ref)
+ end
+ end
+
+ # This method provide a sample data
+ # generated with post_receive_data method
+ # for given project
+ #
+ def sample_data(project, user)
+ @project, @user = project, user
+ @push_commits = project.repository.commits(project.default_branch, nil, 3)
+ post_receive_data(@push_commits.last.id, @push_commits.first.id, "refs/heads/#{project.default_branch}")
+ end
+
+ protected
+
+ def create_push_event
+ Event.create!(
+ project: project,
+ action: Event::PUSHED,
+ data: push_data,
+ author_id: push_data[:user_id]
+ )
+ end
+
+ # Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched,
+ # close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables.
+ def process_commit_messages ref
+ is_default_branch = is_default_branch?(ref)
+
+ @push_commits.each do |commit|
+ # Close issues if these commits were pushed to the project's default branch and the commit message matches the
+ # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
+ # a different branch.
+ issues_to_close = commit.closes_issues(project)
+ author = commit_user(commit)
+
+ if !issues_to_close.empty? && is_default_branch
+ Thread.current[:current_user] = author
+ Thread.current[:current_commit] = commit
+
+ issues_to_close.each { |i| i.close && i.save }
+ end
+
+ # Create cross-reference notes for any other references. Omit any issues that were referenced in an
+ # issue-closing phrase, or have already been mentioned from this commit (probably from this commit
+ # being pushed to a different branch).
+ refs = commit.references(project) - issues_to_close
+ refs.reject! { |r| commit.has_mentioned?(r) }
+ refs.each do |r|
+ Note.create_cross_reference_note(r, commit, author, project)
+ end
+ end
+ end
+
+ # Produce a hash of post-receive data
+ #
+ # data = {
+ # before: String,
+ # after: String,
+ # ref: String,
+ # user_id: String,
+ # user_name: String,
+ # repository: {
+ # name: String,
+ # url: String,
+ # description: String,
+ # homepage: String,
+ # },
+ # commits: Array,
+ # total_commits_count: Fixnum
+ # }
+ #
+ def post_receive_data(oldrev, newrev, ref)
+ # Total commits count
+ push_commits_count = push_commits.size
+
+ # Get latest 20 commits ASC
+ push_commits_limited = push_commits.last(20)
+
+ # Hash to be passed as post_receive_data
+ data = {
+ before: oldrev,
+ after: newrev,
+ ref: ref,
+ user_id: user.id,
+ user_name: user.name,
+ repository: {
+ name: project.name,
+ url: project.url_to_repo,
+ description: project.description,
+ homepage: project.web_url,
+ },
+ commits: [],
+ total_commits_count: push_commits_count
+ }
+
+ # For performance purposes maximum 20 latest commits
+ # will be passed as post receive hook data.
+ #
+ push_commits_limited.each do |commit|
+ data[:commits] << {
+ id: commit.id,
+ message: commit.safe_message,
+ timestamp: commit.committed_date.xmlschema,
+ url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{commit.id}",
+ author: {
+ name: commit.author_name,
+ email: commit.author_email
+ }
+ }
+ end
+
+ data
+ end
+
+ def push_to_existing_branch? ref, oldrev
+ ref_parts = ref.split('/')
+
+ # Return if this is not a push to a branch (e.g. new commits)
+ ref_parts[1] =~ /heads/ && oldrev != "0000000000000000000000000000000000000000"
+ end
+
+ def push_to_new_branch? ref, oldrev
+ ref_parts = ref.split('/')
+
+ ref_parts[1] =~ /heads/ && oldrev == "0000000000000000000000000000000000000000"
+ end
+
+ def push_to_branch? ref
+ ref =~ /refs\/heads/
+ end
+
+ def is_default_branch? ref
+ ref == "refs/heads/#{project.default_branch}"
+ end
+
+ def commit_user commit
+ User.where(email: commit.author_email).first ||
+ User.where(name: commit.author_name).first ||
+ user
+ end
+end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
new file mode 100644
index 00000000000..750a71aea6b
--- /dev/null
+++ b/app/services/notification_service.rb
@@ -0,0 +1,251 @@
+# NotificationService class
+#
+# Used for notifying users with emails about different events
+#
+# Ex.
+# NotificationService.new.new_issue(issue, current_user)
+#
+class NotificationService
+ # Always notify user about ssh key added
+ # only if ssh key is not deploy key
+ #
+ # This is security email so it will be sent
+ # even if user disabled notifications
+ def new_key(key)
+ if key.user
+ mailer.new_ssh_key_email(key.id)
+ end
+ end
+
+ # When create an issue we should send next emails:
+ #
+ # * issue assignee if his notification level is not Disabled
+ # * project team members with notification level higher then Participating
+ #
+ def new_issue(issue, current_user)
+ new_resource_email(issue, issue.project, 'new_issue_email')
+ end
+
+ # When we close an issue we should send next emails:
+ #
+ # * issue author if his notification level is not Disabled
+ # * issue assignee if his notification level is not Disabled
+ # * project team members with notification level higher then Participating
+ #
+ def close_issue(issue, current_user)
+ close_resource_email(issue, issue.project, current_user, 'closed_issue_email')
+ end
+
+ # When we reassign an issue we should send next emails:
+ #
+ # * issue old assignee if his notification level is not Disabled
+ # * issue new assignee if his notification level is not Disabled
+ #
+ def reassigned_issue(issue, current_user)
+ reassign_resource_email(issue, issue.project, current_user, 'reassigned_issue_email')
+ end
+
+
+ # When create a merge request we should send next emails:
+ #
+ # * mr assignee if his notification level is not Disabled
+ #
+ def new_merge_request(merge_request, current_user)
+ new_resource_email(merge_request, merge_request.target_project, 'new_merge_request_email')
+ end
+
+ # When we reassign a merge_request we should send next emails:
+ #
+ # * merge_request old assignee if his notification level is not Disabled
+ # * merge_request assignee if his notification level is not Disabled
+ #
+ def reassigned_merge_request(merge_request, current_user)
+ reassign_resource_email(merge_request, merge_request.target_project, current_user, 'reassigned_merge_request_email')
+ end
+
+ # When we close a merge request we should send next emails:
+ #
+ # * merge_request author if his notification level is not Disabled
+ # * merge_request assignee if his notification level is not Disabled
+ # * project team members with notification level higher then Participating
+ #
+ def close_mr(merge_request, current_user)
+ close_resource_email(merge_request, merge_request.target_project, current_user, 'closed_merge_request_email')
+ end
+
+ # When we merge a merge request we should send next emails:
+ #
+ # * merge_request author if his notification level is not Disabled
+ # * merge_request assignee if his notification level is not Disabled
+ # * project team members with notification level higher then Participating
+ #
+ def merge_mr(merge_request)
+ 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)
+ end
+ end
+
+ # Notify new user with email after creation
+ def new_user(user)
+ # Don't email omniauth created users
+ mailer.new_user_email(user.id, user.password) unless user.extern_uid?
+ end
+
+ # Notify users on new note in system
+ #
+ # TODO: split on methods and refactor
+ #
+ def new_note(note)
+ # ignore wall messages
+ return true unless note.noteable_type.present?
+
+ # ignore gitlab service messages
+ return true if note.note =~ /\A_Status changed to closed_/
+
+ opts = { noteable_type: note.noteable_type, project_id: note.project_id }
+
+ target = note.noteable
+ if target.respond_to?(:participants)
+ recipients = target.participants
+ else
+ recipients = note.mentioned_users
+ end
+
+ if note.commit_id.present?
+ opts.merge!(commit_id: note.commit_id)
+ recipients << note.commit_author
+ else
+ opts.merge!(noteable_id: note.noteable_id)
+ end
+
+ # Get users who left comment in thread
+ recipients = recipients.concat(User.where(id: Note.where(opts).pluck(:author_id)))
+
+ # Merge project watchers
+ recipients = recipients.concat(project_watchers(note.project)).compact.uniq
+
+ # Reject mutes users
+ recipients = reject_muted_users(recipients, note.project)
+
+ # Reject author
+ recipients.delete(note.author)
+
+ # build notify method like 'note_commit_email'
+ notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
+
+ recipients.each do |recipient|
+ mailer.send(notify_method, recipient.id, note.id)
+ end
+ end
+
+ def new_team_member(users_project)
+ mailer.project_access_granted_email(users_project.id)
+ end
+
+ def update_team_member(users_project)
+ mailer.project_access_granted_email(users_project.id)
+ end
+
+ def new_group_member(users_group)
+ mailer.group_access_granted_email(users_group.id)
+ end
+
+ def update_group_member(users_group)
+ mailer.group_access_granted_email(users_group.id)
+ end
+
+ protected
+
+ # Get project users with WATCH notification level
+ def project_watchers(project)
+ project_watchers = []
+ member_methods = { project => :users_projects }
+ member_methods.merge!(project.group => :users_groups) if project.group
+
+ member_methods.each do |object, member_method|
+ # Get project notification settings since it has higher priority
+ user_ids = object.send(member_method).where(notification_level: Notification::N_WATCH).pluck(:user_id)
+ project_watchers += User.where(id: user_ids)
+
+ # next collect users who use global settings with watch state
+ user_ids = object.send(member_method).where(notification_level: Notification::N_GLOBAL).pluck(:user_id)
+ project_watchers += User.where(id: user_ids, notification_level: Notification::N_WATCH)
+ end
+
+ project_watchers.uniq
+ end
+
+ # Remove users with disabled notifications from array
+ # Also remove duplications and nil recipients
+ def reject_muted_users(users, project = nil)
+ users = users.compact.uniq
+
+ users.reject do |user|
+ next user.notification.disabled? unless project
+
+ tm = project.users_projects.find_by_user_id(user.id)
+
+ if !tm && project.group
+ tm = project.group.users_groups.find_by_user_id(user.id)
+ end
+
+ # reject users who globally disabled notification and has no membership
+ next user.notification.disabled? unless tm
+
+ # reject users who disabled notification in project
+ next true if tm.notification.disabled?
+
+ # reject users who have N_GLOBAL in project and disabled in global settings
+ tm.notification.global? && user.notification.disabled?
+ end
+ end
+
+ def new_resource_email(target, project, method)
+ if target.respond_to?(:participants)
+ recipients = target.participants
+ else
+ recipients = []
+ end
+ recipients = reject_muted_users(recipients, project)
+ recipients = recipients.concat(project_watchers(project)).uniq
+ recipients.delete(target.author)
+
+ recipients.each do |recipient|
+ mailer.send(method, recipient.id, target.id)
+ end
+ end
+
+ def close_resource_email(target, project, current_user, method)
+ recipients = reject_muted_users([target.author, target.assignee], project)
+ recipients = recipients.concat(project_watchers(project)).uniq
+ recipients.delete(current_user)
+
+ recipients.each do |recipient|
+ mailer.send(method, recipient.id, target.id, current_user.id)
+ end
+ end
+
+ def reassign_resource_email(target, project, current_user, method)
+ recipients = User.where(id: [target.assignee_id, target.assignee_id_was])
+
+ # Add watchers to email list
+ recipients = recipients.concat(project_watchers(project))
+
+ # reject users with disabled notifications
+ recipients = reject_muted_users(recipients, project)
+
+ # Reject me from recipients if I reassign an item
+ recipients.delete(current_user)
+
+ recipients.each do |recipient|
+ mailer.send(method, recipient.id, target.id, target.assignee_id_was)
+ end
+ end
+
+ def mailer
+ Notify.delay
+ end
+end
diff --git a/app/services/project_transfer_service.rb b/app/services/project_transfer_service.rb
new file mode 100644
index 00000000000..7150c1c78c0
--- /dev/null
+++ b/app/services/project_transfer_service.rb
@@ -0,0 +1,42 @@
+# ProjectTransferService class
+#
+# Used for transfer project to another namespace
+#
+class ProjectTransferService
+ include Gitlab::ShellAdapter
+
+ class TransferError < StandardError; end
+
+ attr_accessor :project
+
+ def transfer(project, new_namespace)
+ Project.transaction do
+ old_path = project.path_with_namespace
+ new_path = File.join(new_namespace.try(:path) || '', project.path)
+
+ if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present?
+ raise TransferError.new("Project with same path in target namespace already exists")
+ end
+
+ project.namespace = new_namespace
+ project.save!
+
+ # Move main repository
+ unless gitlab_shell.mv_repository(old_path, new_path)
+ raise TransferError.new('Cannot move project')
+ end
+
+ # Move wiki repo also if present
+ gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki")
+
+ # create satellite repo
+ project.ensure_satellite_exists
+
+ # clear project cached events
+ project.reset_events_cache
+
+ true
+ end
+ end
+end
+
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
new file mode 100644
index 00000000000..39aec943f75
--- /dev/null
+++ b/app/services/system_hooks_service.rb
@@ -0,0 +1,62 @@
+class SystemHooksService
+ def self.execute_hooks_for(model, event)
+ execute_hooks(build_event_data(model, event))
+ end
+
+ private
+
+ def self.execute_hooks(data)
+ SystemHook.all.each do |sh|
+ async_execute_hook sh, data
+ end
+ end
+
+ def self.async_execute_hook(hook, data)
+ Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data)
+ end
+
+ def self.build_event_data(model, event)
+ data = {
+ event_name: build_event_name(model, event),
+ created_at: model.created_at
+ }
+
+ case model
+ when Project
+ owner = model.owner
+
+ data.merge!({
+ name: model.name,
+ path: model.path,
+ path_with_namespace: model.path_with_namespace,
+ project_id: model.id,
+ owner_name: owner.name,
+ owner_email: owner.respond_to?(:email) ? owner.email : nil
+ })
+ when User
+ data.merge!({
+ name: model.name,
+ email: model.email
+ })
+ when UsersProject
+ data.merge!({
+ project_name: model.project.name,
+ project_path: model.project.path,
+ project_id: model.project_id,
+ user_name: model.user.name,
+ user_email: model.user.email,
+ project_access: model.human_access
+ })
+ end
+ end
+
+ def self.build_event_name(model, event)
+ case model
+ when UsersProject
+ return "user_add_to_team" if event == :create
+ return "user_remove_from_team" if event == :destroy
+ else
+ "#{model.class.name.downcase}_#{event.to_s}"
+ end
+ end
+end
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index 3dbf2860bd4..98794c9470b 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -8,15 +8,23 @@ class AttachmentUploader < CarrierWave::Uploader::Base
end
def image?
- img_ext = %w(png jpg jpeg)
+ img_ext = %w(png jpg jpeg gif bmp tiff)
if file.respond_to?(:extension)
- img_ext.include?(file.extension)
+ img_ext.include?(file.extension.downcase)
else
# Not all CarrierWave storages respond to :extension
- ext = file.path.split('.').last
+ ext = file.path.split('.').last.downcase
img_ext.include?(ext)
end
rescue
false
end
+
+ def secure_url
+ Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}"
+ end
+
+ def file_storage?
+ self.class.storage == CarrierWave::Storage::File
+ end
end
diff --git a/app/views/admin/resque/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index 499738f9a06..2d4ffc10d5f 100644
--- a/app/views/admin/resque/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -1,4 +1,4 @@
-%h3.page_title Background Jobs
+%h3.page-title Background Jobs
%br
.ui-box
%iframe{src: sidekiq_path, width: '100%', height: 900, style: "border: none"}
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 46a876294ce..9ed788d0d9d 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -1,7 +1,7 @@
.admin_dash.row
.span4
.ui-box
- %h5.title Projects
+ .title Projects
.data.padded
= link_to admin_projects_path do
%h1= Project.count
@@ -9,20 +9,20 @@
= link_to 'New Project', new_project_path, class: "btn btn-small"
.span4
.ui-box
- %h5.title Groups
+ .title Users
.data.padded
- = link_to admin_groups_path do
- %h1= Group.count
+ = link_to admin_users_path do
+ %h1= User.count
%hr
- = link_to 'New Group', new_admin_group_path, class: "btn btn-small"
+ = link_to 'New User', new_admin_user_path, class: "btn btn-small"
.span4
.ui-box
- %h5.title Users
+ .title Groups
.data.padded
- = link_to admin_users_path do
- %h1= User.count
+ = link_to admin_groups_path do
+ %h1= Group.count
%hr
- = link_to 'New User', new_admin_user_path, class: "btn btn-small"
+ = link_to 'New Group', new_admin_group_path, class: "btn btn-small"
.row
.span4
@@ -50,6 +50,10 @@
%h4 Stats
%hr
%p
+ Forks
+ %span.light.pull-right
+ = ForkedProjectLink.count
+ %p
Issues
%span.light.pull-right
= Issue.count
diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml
index dce044956c3..8ed463a8191 100644
--- a/app/views/admin/groups/edit.html.haml
+++ b/app/views/admin/groups/edit.html.haml
@@ -1,22 +1,25 @@
-%h3.page_title Rename Group
+%h3.page-title Edit Group
%hr
= form_for [:admin, @group] do |f|
- if @group.errors.any?
.alert.alert-error
%span= @group.errors.full_messages.first
- .clearfix.group_name_holder
+ .control-group.group_name_holder
= f.label :name do
Group name is
- .input
- = f.text_field :name, placeholder: "Example Group", class: "xxlarge"
+ .controls
+ = f.text_field :name, placeholder: "Example Group", class: "input-xxlarge"
+ .control-group.group-description-holder
+ = f.label :description, "Details"
+ .controls
+ = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4
-
- .clearfix.group_name_holder
+ .control-group.group_name_holder
= f.label :path do
%span.cred Group path is
- .input
- = f.text_field :path, placeholder: "example-group", class: "xxlarge danger"
+ .controls
+ = f.text_field :path, placeholder: "example-group", class: "input-xxlarge danger"
%ul.cred
%li Changing group path can have unintended side effects.
%li Renaming group path will rename directory for all related projects
@@ -24,5 +27,5 @@
%li It will change the git path to repositories under this group.
.form-actions
- = f.submit 'Rename group', class: "btn btn-remove"
+ = f.submit 'Save changes', class: "btn btn-primary"
= link_to 'Cancel', admin_groups_path, class: "btn btn-cancel"
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 25ce66575bf..c9d7c24f204 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -1,35 +1,45 @@
-%h3.page_title
- Groups
+%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-small pull-right"
+ = link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right"
%br
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
- = text_field_tag :name, params[:name], class: "xlarge"
+ = text_field_tag :name, params[:name], class: "span6"
= submit_tag "Search", class: "btn submit btn-primary"
-%table
- %thead
- %tr
- %th
- Name
- %i.icon-sort-down
- %th Path
- %th Projects
- %th Owner
- %th.cred Danger Zone!
+%hr
+%ul.bordered-list
- @groups.each do |group|
- %tr
- %td
- %strong= link_to group.name, [:admin, group]
- %td= group.path
- %td= group.projects.count
- %td
- = link_to group.owner_name, admin_user_path(group.owner_id)
- %td.bgred
- = link_to 'Rename', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small"
- = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
-= paginate @groups, theme: "admin"
+ %li
+ .clearfix
+ .pull-right.prepend-top-10
+ = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small"
+ = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
+
+ %h4
+ = link_to [:admin, group] do
+ = 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
+
+= paginate @groups, theme: "gitlab"
diff --git a/app/views/admin/groups/new.html.haml b/app/views/admin/groups/new.html.haml
index 60c6fa5ad09..0ae35eb6b43 100644
--- a/app/views/admin/groups/new.html.haml
+++ b/app/views/admin/groups/new.html.haml
@@ -1,21 +1,27 @@
-%h3.page_title New Group
+%h3.page-title New Group
%hr
= form_for [:admin, @group] do |f|
- if @group.errors.any?
.alert.alert-error
%span= @group.errors.full_messages.first
- .clearfix
+ .control-group
= f.label :name do
Group name is
- .input
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
- &nbsp;
- = f.submit 'Create group', class: "btn btn-primary"
+ .controls
+ = f.text_field :name, placeholder: "Ex. OpenSource", class: "input-xxlarge left"
+ .control-group.group-description-holder
+ = f.label :description, "Details"
+ .controls
+ = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4
+
+ .form-actions
+ = f.submit 'Create group', class: "btn btn-create"
+
%hr
.padded
%ul
%li Group is kind of directory for several projects
%li All created groups are private
%li People within a group see only projects they have access to
- %li All projects of group will be stored in group directory
+ %li All projects of group will be stored in a group directory
%li You will be able to move existing projects into group
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 90f8fc0f814..1566c345809 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -1,127 +1,76 @@
-%h3.page_title
+%h3.page-title
Group: #{@group.name}
-%br
-%table.zebra-striped
- %thead
- %tr
- %th Group
- %th
- %tr
- %td
- %b
- Name:
- %td
- = @group.name
- &nbsp;
- = link_to edit_admin_group_path(@group), class: "btn btn-small pull-right" do
- %i.icon-edit
- Rename
- %tr
- %td
- %b
- Path:
- %td
- %span.monospace= File.join(Gitlab.config.gitlab_shell.repos_path, @group.path)
- %tr
- %td
- %b
- Owner:
- %td
- = @group.owner_name
- .pull-right
- = link_to "#", class: "btn btn-small change-owner-link" do
- %i.icon-edit
- Change owner
+ = link_to edit_admin_group_path(@group), class: "btn btn-small pull-right" do
+ %i.icon-edit
+ Edit
+%hr
+.row
+ .span6
+ .ui-box
+ .title
+ Group info:
+ %ul.well-list
+ %li
+ %span.light Name:
+ %strong= @group.name
+ %li
+ %span.light Path:
+ %strong
+ = @group.path
- %tr.change-owner-holder.hide
- %td.bgred
- %b.cred
- New Owner:
- %td.bgred
- = form_for [:admin, @group] do |f|
- = f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
- %div
- = f.submit 'Change Owner', class: "btn btn-remove"
- = link_to "Cancel", "#", class: "btn change-owner-cancel-link"
+ %li
+ %span.light Description:
+ %strong
+ = @group.description
-- if @group.projects.any?
- %fieldset
- %legend Projects (#{@group.projects.count})
- %table
- %thead
- %tr
- %th Project name
- %th Path
- %th Users
- %th.cred Danger Zone!
- - @group.projects.each do |project|
- %tr
- %td
- = link_to project.name_with_namespace, [:admin, project]
- %td
- %span.monospace= project.path_with_namespace + ".git"
- %td= project.users.count
- %td.bgred
- = link_to 'Transfer project to global namespace', remove_project_admin_group_path(@group, project_id: project.id), confirm: 'Remove project from group and move to global namespace. Are you sure?', method: :delete, class: "btn btn-remove small"
+ %li
+ %span.light Created at:
+ %strong
+ = @group.created_at.stamp("March 1, 1999")
- = form_tag project_teams_update_admin_group_path(@group), id: "new_team_member", class: "bulk_import", method: :put do
- %table.zebra-striped
- %thead
- %tr
- %th Users
- %th Project Access:
-
- - @group.users.each do |user|
- - next unless user
- %tr{class: "user_#{user.id}"}
- %td.name= link_to user.name, admin_user_path(user)
- %td.projects_access
- - user.authorized_projects.in_namespace(@group).each do |project|
- - u_p = user.users_projects.in_project(project).first
- - next unless u_p
- %span
- = project.name_with_namespace
- = link_to "(#{ u_p.project_access_human })", edit_admin_project_member_path(project, user)
- %tr
- %td.input= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
- %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
+ .ui-box
+ .title
+ Projects
+ %small
+ (#{@group.projects.count})
+ %ul.well-list
+ - @group.projects.sort_by(&:name).each do |project|
+ %li
+ %strong
+ = link_to project.name_with_namespace, [:admin, project]
+ %span.pull-right.light
+ %span.monospace= project.path_with_namespace + ".git"
- %tr
- %td= submit_tag 'Add user to projects in group', class: "btn btn-primary"
- %td
+ .span6
+ .ui-box
+ .title
+ Add user(s) to the group:
+ .ui-box-body.form-holder
+ %p.light
Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink"
-- else
- %fieldset
- %legend Group is empty
-
-= form_tag project_update_admin_group_path(@group), class: "bulk_import", method: :put do
- %fieldset
- %legend Move projects to group
- .alert
- You can move only projects with existing repos
- %br
- Group projects will be moved in group directory and will not be accessible by old path
- .clearfix
- = label_tag :project_ids do
- Projects
- .input
- = select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
- .form-actions
- = submit_tag 'Add', class: "btn btn-primary"
-
-:javascript
- $(function(){
- var modal = $('.change-owner-holder');
- $('.change-owner-link').bind("click", function(){
- $(this).hide();
- modal.show();
- });
- $('.change-owner-cancel-link').bind("click", function(){
- modal.hide();
- $('.change-owner-link').show();
- })
- })
-
+ = form_tag project_teams_update_admin_group_path(@group), id: "new_team_member", class: "bulk_import", method: :put do
+ %div
+ = users_select_tag(:user_ids, multiple: true)
+ %div.prepend-top-10
+ = select_tag :group_access, options_for_select(UsersGroup.group_access_roles), class: "project-access-select chosen"
+ %hr
+ = submit_tag 'Add users into group', class: "btn btn-create"
+ .ui-box
+ .title
+ %strong #{@group.name}
+ Group Members
+ %small
+ (#{@group.users_groups.count})
+ %ul.well-list.group-users-list
+ - @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)
+ %span.pull-right.light
+ = member.human_access
+ = link_to group_users_group_path(@group, member), 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
diff --git a/app/views/admin/hooks/_data_ex.html.erb b/app/views/admin/hooks/_data_ex.html.erb
index 652ee5aa56f..b69aa92716d 100644
--- a/app/views/admin/hooks/_data_ex.html.erb
+++ b/app/views/admin/hooks/_data_ex.html.erb
@@ -1,23 +1,26 @@
<% data_ex_str = <<eos
1. Project created:
{
- "created_at": "2012-07-21T07:30:54Z",
- "event_name": "project_create",
- "name": "StoreCloud",
- "owner_email": "johnsmith@gmail.com",
- "owner_name": "John Smith",
- "path": "storecloud",
- "project_id": 74
+ "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:
{
- "event_name": "project_destroy",
- "name": "Underscore",
- "owner_email": "johnsmith@gmail.com",
- "owner_name": "John Smith",
- "path": "underscore",
- "project_id": 73
+ "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:
@@ -28,8 +31,8 @@
"project_id": 74,
"project_name": "StoreCloud",
"project_path": "storecloud",
- "owner_email": "johnsmith@gmail.com",
- "owner_name": "John Smith",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
}
4. Team Member Removed:
@@ -40,8 +43,8 @@
"project_id": 74,
"project_name": "StoreCloud",
"project_path": "storecloud",
- "owner_email": "johnsmith@gmail.com",
- "owner_name": "John Smith",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
}
5. User created:
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index acbf7a108b8..eb6570af30e 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -1,6 +1,6 @@
.alert.alert-info
%span
- Post receive hooks for binding events.
+ Post-receive hooks for binding events.
%br
Read more about system hooks
%strong #{link_to "here", help_system_hooks_path, class: "vlink"}
@@ -10,12 +10,12 @@
.alert.alert-error
- @hook.errors.full_messages.each do |msg|
%p= msg
- .clearfix
+ .control-group
= f.label :url, "URL:"
- .input
- = f.text_field :url, class: "text_field xxlarge"
+ .controls
+ = f.text_field :url, class: "text_field input-xxlarge input-xpadding"
&nbsp;
- = f.submit "Add System Hook", class: "btn btn-primary"
+ = f.submit "Add System Hook", class: "btn btn-create"
%hr
-if @hooks.any?
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index 9ddd781c6ec..cce8aeb02c7 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -8,60 +8,60 @@
%li
= link_to "sidekiq.log", "#sidekiq", 'data-toggle' => 'tab'
-%p.light To prevent perfomance issues admin logs output the last 2000 lines
+%p.light To prevent performance issues admin logs output the last 2000 lines
.tab-content
.tab-pane.active#githost
- .file_holder#README
- .file_title
+ .file-holder#README
+ .file-title
%i.icon-file
githost.log
.pull-right
= link_to '#', class: 'log-bottom' do
%i.icon-arrow-down
Scroll down
- .file_content.logs
+ .file-content.logs
%ol
- Gitlab::GitLogger.read_latest.each do |line|
%li
%p= line
.tab-pane#application
- .file_holder#README
- .file_title
+ .file-holder#README
+ .file-title
%i.icon-file
application.log
.pull-right
= link_to '#', class: 'log-bottom' do
%i.icon-arrow-down
Scroll down
- .file_content.logs
+ .file-content.logs
%ol
- Gitlab::AppLogger.read_latest.each do |line|
%li
%p= line
.tab-pane#production
- .file_holder#README
- .file_title
+ .file-holder#README
+ .file-title
%i.icon-file
production.log
.pull-right
= link_to '#', class: 'log-bottom' do
%i.icon-arrow-down
Scroll down
- .file_content.logs
+ .file-content.logs
%ol
- Gitlab::Logger.read_latest_for('production.log').each do |line|
%li
%p= line
.tab-pane#sidekiq
- .file_holder#README
- .file_title
+ .file-holder#README
+ .file-title
%i.icon-file
sidekiq.log
.pull-right
= link_to '#', class: 'log-bottom' do
%i.icon-arrow-down
Scroll down
- .file_content.logs
+ .file-content.logs
%ol
- Gitlab::Logger.read_latest_for('sidekiq.log').each do |line|
%li
diff --git a/app/views/admin/projects/_form.html.haml b/app/views/admin/projects/_form.html.haml
deleted file mode 100644
index ebf69924a25..00000000000
--- a/app/views/admin/projects/_form.html.haml
+++ /dev/null
@@ -1,77 +0,0 @@
-= form_for [:admin, project] do |f|
- -if project.errors.any?
- .alert.alert-error
- %ul
- - project.errors.full_messages.each do |msg|
- %li= msg
-
- .clearfix.project_name_holder
- = f.label :name do
- Project name is
- .input
- = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
-
- - if project.repo_exists?
- %fieldset.adv_settings
- %legend Advanced settings:
- .clearfix
- = f.label :path do
- Path
- .input
- = text_field_tag :ppath, @project.repository.path_to_repo, class: "xlarge", disabled: true
-
- .clearfix
- = f.label :default_branch, "Default Branch"
- .input= f.select(:default_branch, @project.repository.heads.map(&:name), {}, style: "width:210px;")
-
- %fieldset.adv_settings
- %legend Features:
-
- .clearfix
- = f.label :issues_enabled, "Issues"
- .input= f.check_box :issues_enabled
-
- .clearfix
- = f.label :merge_requests_enabled, "Merge Requests"
- .input= f.check_box :merge_requests_enabled
-
- .clearfix
- = f.label :wall_enabled, "Wall"
- .input= f.check_box :wall_enabled
-
- .clearfix
- = f.label :wiki_enabled, "Wiki"
- .input= f.check_box :wiki_enabled
-
- %fieldset.features
- %legend Public mode:
- .clearfix
- = f.label :public do
- %span Allow public http clone
- .input= f.check_box :public
-
- %fieldset.features
- %legend Transfer:
- .control-group
- = f.label :namespace_id do
- %span Namespace
- .controls
- = f.select :namespace_id, namespaces_options(@project.namespace_id, :all), {}, {class: 'chosen'}
- %br
- %ul.prepend-top-10.cred
- %li Be careful. Changing project namespace can have unintended side effects
- %li You can transfer project only to namespaces you can manage
- %li You will need to update your local repositories to point to the new location.
-
-
- .actions
- = f.submit 'Save Project', class: "btn btn-save"
- = link_to 'Cancel', admin_projects_path, class: "btn btn-cancel"
-
-
-
-:javascript
- $(function(){
- new Projects();
- })
-
diff --git a/app/views/admin/projects/edit.html.haml b/app/views/admin/projects/edit.html.haml
deleted file mode 100644
index 7b59a0cc753..00000000000
--- a/app/views/admin/projects/edit.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-%h3.page_title #{@project.name} &rarr; Edit project
-%hr
-= render 'form', project: @project
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 15b2778252a..bc297209ae5 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -1,9 +1,3 @@
-%h3.page_title
- Projects
- = link_to 'New Project', new_project_path, class: "btn btn-small pull-right"
-
-%hr
-
.row
.span4
.admin-filter
@@ -14,9 +8,9 @@
= text_field_tag :name, params[:name], class: "span2"
.control-group
- = label_tag :namespace_id, 'Namespace:', class: 'control-label'
+ = label_tag :owner_id, 'Owner:', class: 'control-label'
.controls
- = select_tag :namespace_id, namespaces_options(params[:namespace_id], :all), class: "chosen span2", prompt: "Any"
+ = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large'
.control-group
= label_tag :public_only, 'Public Only', class: 'control-label'
.controls
@@ -41,21 +35,21 @@
= link_to "Reset", admin_projects_path, class: "btn"
.span8
.ui-box
- %h5.title
+ .title
Projects (#{@projects.total_count})
+ .pull-right
+ = link_to 'New Project', new_project_path, class: "btn btn-new"
%ul.well-list
- @projects.each do |project|
%li
- if project.public
- %i.icon-share
+ = public_icon
- else
- %i.icon-lock.cgreen
+ = private_icon
= link_to project.name_with_namespace, [:admin, project]
.pull-right
- = link_to 'Edit', edit_admin_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
- = link_to 'Destroy', [:admin, project], confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
+ = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
+ = link_to 'Destroy', [project], confirm: remove_project_message(project), method: :delete, class: "btn btn-small btn-remove"
- if @projects.blank?
%p.nothing_here_message 0 projects matches
- - else
- %li.bottom
- = paginate @projects, theme: "gitlab"
+ = paginate @projects, theme: "gitlab"
diff --git a/app/views/admin/projects/members/_form.html.haml b/app/views/admin/projects/members/_form.html.haml
deleted file mode 100644
index 8041202980d..00000000000
--- a/app/views/admin/projects/members/_form.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-= form_for @team_member_relation, as: :team_member, url: admin_project_member_path(@project, @member) do |f|
- -if @team_member_relation.errors.any?
- .alert.alert-error
- %ul
- - @team_member_relation.errors.full_messages.each do |msg|
- %li= msg
-
- .clearfix
- %label Project Access:
- .input
- = f.select :project_access, options_for_select(Project.access_options, @team_member_relation.project_access), {}, class: "project-access-select chosen span3"
-
- %br
- .actions
- = f.submit 'Save', class: "btn btn-primary"
- = link_to 'Cancel', :back, class: "btn"
diff --git a/app/views/admin/projects/members/edit.html.haml b/app/views/admin/projects/members/edit.html.haml
deleted file mode 100644
index 2d76deb2aca..00000000000
--- a/app/views/admin/projects/members/edit.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-%p.slead
- Edit access for
- = link_to @member.name, admin_user_path(@member)
- in
- = link_to @project.name_with_namespace, admin_project_path(@project)
-
-%hr
-= render 'form'
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 65b921170fd..c8a87328207 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -1,129 +1,111 @@
-%h3.page_title
+%h3.page-title
Project: #{@project.name_with_namespace}
- = link_to edit_admin_project_path(@project), class: "btn pull-right" do
+ = link_to edit_project_path(@project), class: "btn pull-right" do
%i.icon-edit
Edit
+%hr
+.row
+ .span6
+ .ui-box
+ .title
+ Project info:
+ %ul.well-list
+ %li
+ %span.light Name:
+ %strong
+ = link_to @project.name, project_path(@project)
+ %li
+ %span.light Namespace:
+ %strong
+ - if @project.namespace
+ = link_to @project.namespace.human_name, [:admin, @project.group || @project.owner]
+ - else
+ Global
+ %li
+ %span.light Owned by:
+ %strong
+ - if @project.owner
+ = link_to @project.owner_name, [:admin, @project.owner]
+ - else
+ (deleted)
+ %li
+ %span.light Created by:
+ %strong
+ = @project.creator.try(:name) || '(deleted)'
-%br
-%table.zebra-striped
- %thead
- %tr
- %th Project
- %th
- %tr
- %td
- %b
- Name:
- %td
- = @project.name
- %tr
- %td
- %b
- Namespace:
- %td
- - if @project.namespace
- = @project.namespace.human_name
- - else
- Global
- %tr
- %td
- %b
- Owned by:
- %td
- - if @project.owner
- = link_to @project.owner_name, admin_user_path(@project.owner)
- - else
- (deleted)
- %tr
- %td
- %b
- Created by:
- %td
- = @project.creator.try(:name) || '(deleted)'
- %tr
- %td
- %b
- Created at:
- %td
- = @project.created_at.stamp("March 1, 1999")
- %tr
- %td
- %b
- Smart HTTP:
- %td
- = link_to @project.http_url_to_repo
- %tr
- %td
- %b
- SSH:
- %td
- = link_to @project.ssh_url_to_repo
- - if @project.public
- %tr.bgred
- %td
- %b
- Public Read-Only Code access:
- %td
- = check_box_tag 'public', nil, @project.public
+ %li
+ %span.light Created at:
+ %strong
+ = @project.created_at.stamp("March 1, 1999")
-- if @repository
- %table.zebra-striped
- %thead
- %tr
- %th Repository
- %th
- %tr
- %td
- %b
- FS Path:
- %td
- %code= @repository.path_to_repo
- %tr
- %td
- %b
- Last commit at:
- %td
- = last_commit(@project)
+ %li
+ %span.light http:
+ %strong
+ = link_to @project.http_url_to_repo
+ %li
+ %span.light ssh:
+ %strong
+ = link_to @project.ssh_url_to_repo
+ - if @project.repository.exists?
+ %li
+ %span.light fs:
+ %strong
+ = @repository.path_to_repo
-%br
-%h5
- Team
- %small
- (#{@project.users.count})
-%br
-%table.zebra-striped.team_members
- %thead
- %tr
- %th Name
- %th Project Access
- %th Repository Access
- %th
+ %li
+ %span.light last commit:
+ %strong
+ = last_commit(@project)
+ - else
+ %li
+ %span.light repository:
+ %strong.cred
+ does not exist
- - @project.users.each do |tm|
- %tr
- %td
- = link_to tm.name, admin_user_path(tm)
- %td= @project.project_access_human(tm)
- %td= link_to 'Edit Access', edit_admin_project_member_path(@project, tm), class: "btn btn-small"
- %td= link_to 'Remove from team', admin_project_member_path(@project, tm), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove small"
+ %li
+ %span.light access:
+ %strong
+ - if @project.public
+ %span.cblue
+ %i.icon-share
+ Public
+ - else
+ %span.cgreen
+ %i.icon-lock
+ Private
+ .span6
+ - if @group
+ .ui-box
+ .title
+ %strong #{@group.name}
+ group members (#{@group.users_groups.count})
+ .pull-right
+ = link_to admin_group_path(@group), class: 'btn btn-small' do
+ %i.icon-edit
+ %ul.well-list
+ - @group.users_groups.order('group_access DESC').each do |member|
+ = render 'users_groups/users_group', member: member, show_controls: false
-%br
-%h5 Add new team member
-%br
-= form_tag team_update_admin_project_path(@project), class: "bulk_import", method: :put do
- %table.zebra-striped
- %thead
- %tr
- %th Users
- %th Project Access:
-
- %tr
- %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
- %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
-
- %tr
- %td= submit_tag 'Add', class: "btn btn-primary"
- %td
- Read more about project permissions
- %strong= link_to "here", help_permissions_path, class: "vlink"
+ .ui-box
+ .title
+ Project members
+ %small
+ (#{@project.users.count})
+ .pull-right
+ = link_to project_team_index_path(@project), class: "btn btn-tiny" do
+ %i.icon-edit
+ Manage Access
+ %ul.well-list.team_members
+ - @project.users_projects.each do |users_project|
+ - user = users_project.user
+ %li.users_project
+ %strong
+ = link_to user.name, admin_user_path(user)
+ .pull-right
+ - if users_project.owner?
+ %span.light Owner
+ - else
+ %span.light= users_project.human_access
+ = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, remote: true, class: "btn btn-small btn-remove" do
+ %i.icon-remove
diff --git a/app/views/admin/teams/edit.html.haml b/app/views/admin/teams/edit.html.haml
deleted file mode 100644
index 9282398ce5b..00000000000
--- a/app/views/admin/teams/edit.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-%h3.page_title Rename Team
-%hr
-= form_for @team, url: admin_team_path(@team), method: :put do |f|
- - if @team.errors.any?
- .alert.alert-error
- %span= @team.errors.full_messages.first
- .clearfix.team_name_holder
- = f.label :name do
- Team name is
- .input
- = f.text_field :name, placeholder: "Example Team", class: "xxlarge"
-
- .clearfix.team_name_holder
- = f.label :path do
- %span.cred Team path is
- .input
- = f.text_field :path, placeholder: "example-team", class: "xxlarge danger"
- %ul.cred
- %li It will change web url for access team and team projects.
-
- .form-actions
- = f.submit 'Rename team', class: "btn btn-remove"
- = link_to 'Cancel', admin_teams_path, class: "btn btn-cancel"
diff --git a/app/views/admin/teams/index.html.haml b/app/views/admin/teams/index.html.haml
deleted file mode 100644
index 1f2f4763f76..00000000000
--- a/app/views/admin/teams/index.html.haml
+++ /dev/null
@@ -1,38 +0,0 @@
-%h3.page_title
- Teams
- %small
- simple Teams description
-
- = link_to 'New Team', new_admin_team_path, class: "btn btn-small pull-right"
- %br
-
-= form_tag admin_teams_path, method: :get, class: 'form-inline' do
- = text_field_tag :name, params[:name], class: "xlarge"
- = submit_tag "Search", class: "btn submit btn-primary"
-
-%table
- %thead
- %tr
- %th
- Name
- %i.icon-sort-down
- %th Path
- %th Projects
- %th Members
- %th Owner
- %th.cred Danger Zone!
-
- - @teams.each do |team|
- %tr
- %td
- %strong= link_to team.name, admin_team_path(team)
- %td= team.path
- %td= team.projects.count
- %td= team.members.count
- %td
- = link_to team.owner.name, admin_user_path(team.owner_id)
- %td.bgred
- = link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small"
- = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
-
-= paginate @teams, theme: "admin"
diff --git a/app/views/admin/teams/members/_form.html.haml b/app/views/admin/teams/members/_form.html.haml
deleted file mode 100644
index f1388aab4bb..00000000000
--- a/app/views/admin/teams/members/_form.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-= form_tag admin_team_member_path(@team, @member), method: :put do
- -if @member.errors.any?
- .alert.alert-error
- %ul
- - @member.errors.full_messages.each do |msg|
- %li= msg
-
- .clearfix
- %label Default access for Team projects:
- .input
- = select_tag :default_project_access, options_for_select(UserTeam.access_roles, @team.default_projects_access(@member)), class: "project-access-select chosen span3"
- .clearfix
- %label Team admin?
- .input
- = check_box_tag :group_admin, true, @team.admin?(@member)
-
- %br
- .actions
- = submit_tag 'Save', class: "btn btn-primary"
- = link_to 'Cancel', :back, class: "btn"
diff --git a/app/views/admin/teams/members/edit.html.haml b/app/views/admin/teams/members/edit.html.haml
deleted file mode 100644
index a82847ee5f8..00000000000
--- a/app/views/admin/teams/members/edit.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-%h3
- Edit access #{@member.name} in #{@team.name} team
-
-%hr
-%table.zebra-striped
- %tr
- %td User:
- %td= @member.name
- %tr
- %td Team:
- %td= @team.name
- %tr
- %td Since:
- %td= member_since(@team, @member).stamp("Nov 11, 2010")
-
-= render 'form'
diff --git a/app/views/admin/teams/members/new.html.haml b/app/views/admin/teams/members/new.html.haml
deleted file mode 100644
index a37c941db53..00000000000
--- a/app/views/admin/teams/members/new.html.haml
+++ /dev/null
@@ -1,29 +0,0 @@
-%h3.page_title
- Team: #{@team.name}
-
-%fieldset
- %legend Members (#{@team.members.count})
- = form_tag admin_team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do
- %table#members_list
- %thead
- %tr
- %th User name
- %th Default project access
- %th Team access
- %th
- - @team.members.each do |member|
- %tr.member
- %td
- = link_to [:admin, member] do
- = member.name
- %small= "(#{member.email})"
- %td= @team.human_default_projects_access(member)
- %td= @team.admin?(member) ? "Admin" : "Member"
- %td
- %tr
- %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_email), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
- %td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
- %td
- %span= check_box_tag :group_admin
- %span Admin?
- %td= submit_tag 'Add', class: "btn btn-primary", id: :add_members_to_team
diff --git a/app/views/admin/teams/new.html.haml b/app/views/admin/teams/new.html.haml
deleted file mode 100644
index 5d55a7975ee..00000000000
--- a/app/views/admin/teams/new.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-%h3.page_title New Team
-%hr
-= form_for @team, url: admin_teams_path do |f|
- - if @team.errors.any?
- .alert.alert-error
- %span= @team.errors.full_messages.first
- .clearfix
- = f.label :name do
- Team name is
- .input
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
- &nbsp;
- = f.submit 'Create team', class: "btn btn-primary"
- %hr
- .padded
- %ul
- %li All created teams are public (users can view who enter into team and which project are assigned for this team)
- %li People within a team see only projects they have access to
- %li You will be able to assign existing projects for team
diff --git a/app/views/admin/teams/projects/_form.html.haml b/app/views/admin/teams/projects/_form.html.haml
deleted file mode 100644
index 5b79d518d42..00000000000
--- a/app/views/admin/teams/projects/_form.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-= form_tag admin_team_project_path(@team, @project), method: :put do
- -if @project.errors.any?
- .alert.alert-error
- %ul
- - @project.errors.full_messages.each do |msg|
- %li= msg
-
- .clearfix
- %label Max access for Team members:
- .input
- = select_tag :greatest_project_access, options_for_select(UserTeam.access_roles, @team.max_project_access(@project)), class: "project-access-select chosen span3"
-
- %br
- .actions
- = submit_tag 'Save', class: "btn btn-primary"
- = link_to 'Cancel', :back, class: "btn"
diff --git a/app/views/admin/teams/projects/edit.html.haml b/app/views/admin/teams/projects/edit.html.haml
deleted file mode 100644
index b91a4982b81..00000000000
--- a/app/views/admin/teams/projects/edit.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-%h3
- Edit max access in #{@project.name} for #{@team.name} team
-
-%hr
-%table.zebra-striped
- %tr
- %td Project:
- %td= @project.name
- %tr
- %td Team:
- %td= @team.name
- %tr
- %td Since:
- %td= assigned_since(@team, @project).stamp("Nov 11, 2010")
-
-= render 'form'
diff --git a/app/views/admin/teams/projects/new.html.haml b/app/views/admin/teams/projects/new.html.haml
deleted file mode 100644
index b60dad35214..00000000000
--- a/app/views/admin/teams/projects/new.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-%h3.page_title
- Team: #{@team.name}
-
-%fieldset
- %legend Projects (#{@team.projects.count})
- = form_tag admin_team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do
- %table#projects_list
- %thead
- %tr
- %th Project name
- %th Max access
- %th
- - @team.projects.each do |project|
- %tr.project
- %td
- = link_to project.name_with_namespace, [:admin, project]
- %td
- %span= @team.human_max_project_access(project)
- %td
- %tr
- %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
- %td= select_tag :greatest_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
- %td= submit_tag 'Add', class: "btn btn-primary", id: :assign_projects_to_team
diff --git a/app/views/admin/teams/show.html.haml b/app/views/admin/teams/show.html.haml
deleted file mode 100644
index e5d079981c0..00000000000
--- a/app/views/admin/teams/show.html.haml
+++ /dev/null
@@ -1,101 +0,0 @@
-%h3.page_title
- Team: #{@team.name}
-
-%br
-%table.zebra-striped
- %thead
- %tr
- %th Team
- %th
- %tr
- %td
- %b
- Name:
- %td
- = @team.name
- &nbsp;
- = link_to edit_admin_team_path(@team), class: "btn btn-small pull-right" do
- %i.icon-edit
- Rename
- %tr
- %td
- %b
- Owner:
- %td
- = @team.owner.name
- .pull-right
- = link_to "#", class: "btn btn-small change-owner-link" do
- %i.icon-edit
- Change owner
-
- %tr.change-owner-holder.hide
- %td.bgred
- %b.cred
- New Owner:
- %td.bgred
- = form_for @team, url: admin_team_path(@team) do |f|
- = f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
- %div
- = f.submit 'Change Owner', class: "btn btn-remove"
- = link_to "Cancel", "#", class: "btn change-owner-cancel-link"
-
-%fieldset
- %legend
- Members (#{@team.members.count})
- %span= link_to 'Add members', new_admin_team_member_path(@team), class: "btn btn-primary btn-small pull-right", id: :add_members_to_team
- - if @team.members.any?
- %table#members_list
- %thead
- %tr
- %th User name
- %th Default project access
- %th Team access
- %th.cred.span3 Danger Zone!
- - @team.members.each do |member|
- %tr.member{ class: "user_#{member.id}"}
- %td
- = link_to [:admin, member] do
- = member.name
- %small= "(#{member.email})"
- %td= @team.human_default_projects_access(member)
- %td= @team.admin?(member) ? "Admin" : "Member"
- %td.bgred
- = link_to 'Edit', edit_admin_team_member_path(@team, member), class: "btn btn-small"
- &nbsp;
- = link_to 'Remove', admin_team_member_path(@team, member), confirm: 'Remove member from team. Are you sure?', method: :delete, class: "btn btn-remove btn-small", id: "remove_member_#{member.id}"
-
-%fieldset
- %legend
- Projects (#{@team.projects.count})
- %span= link_to 'Add projects', new_admin_team_project_path(@team), class: "btn btn-primary btn-small pull-right", id: :assign_projects_to_team
- - if @team.projects.any?
- %table#projects_list
- %thead
- %tr
- %th Project name
- %th Max access
- %th.cred.span3 Danger Zone!
- - @team.projects.each do |project|
- %tr.project
- %td
- = link_to project.name_with_namespace, [:admin, project]
- %td
- %span= @team.human_max_project_access(project)
- %td.bgred
- = link_to 'Edit', edit_admin_team_project_path(@team, project), class: "btn btn-small"
- &nbsp;
- = link_to 'Relegate', admin_team_project_path(@team, project), confirm: 'Remove project from team. Are you sure?', method: :delete, class: "btn btn-remove small", id: "relegate_project_#{project.id}"
-
-:javascript
- $(function(){
- var modal = $('.change-owner-holder');
- $('.change-owner-link').bind("click", function(){
- $(this).hide();
- modal.show();
- });
- $('.change-owner-cancel-link').bind("click", function(){
- modal.hide();
- $('.change-owner-link').show();
- })
- })
-
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index 48876338a23..3f930c45fa6 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -1,87 +1,90 @@
.user_new
- = form_for [:admin, @admin_user] do |f|
- -if @admin_user.errors.any?
+ = form_for [:admin, @user] do |f|
+ -if @user.errors.any?
#error_explanation
%ul.unstyled.alert.alert-error
- - @admin_user.errors.full_messages.each do |msg|
+ - @user.errors.full_messages.each do |msg|
%li= msg
%fieldset
%legend Account
- .clearfix
+ .control-group
= f.label :name
- .input
+ .controls
= f.text_field :name, required: true, autocomplete: "off"
%span.help-inline * required
- .clearfix
+ .control-group
= f.label :username
- .input
+ .controls
= f.text_field :username, required: true, autocomplete: "off"
%span.help-inline * required
- .clearfix
+ .control-group
= f.label :email
- .input
+ .controls
= f.text_field :email, required: true, autocomplete: "off"
%span.help-inline * required
- %fieldset
- %legend Password
- .clearfix
- = f.label :password
- .input= f.password_field :password, disabled: f.object.force_random_password
- .clearfix
- = f.label :password_confirmation
- .input= f.password_field :password_confirmation, disabled: f.object.force_random_password
- -if f.object.new_record?
- .clearfix
- = f.label :force_random_password do
- %span Generate random password
- .input= f.check_box :force_random_password, {}, true, nil
+ - if @user.new_record?
+ %fieldset
+ %legend Password
+ .control-group
+ = f.label :password
+ .controls
+ %strong
+ A temporary password will be generated and sent to user.
+ %br
+ User will be forced to change it after first sign in
+ - else
+ %fieldset
+ %legend Password
+ .control-group
+ = f.label :password
+ .controls= f.password_field :password, disabled: f.object.force_random_password
+ .control-group
+ = f.label :password_confirmation
+ .controls= f.password_field :password_confirmation, disabled: f.object.force_random_password
%fieldset
%legend Access
.row
.span8
- .clearfix
+ .control-group
= f.label :projects_limit
- .input= f.number_field :projects_limit
+ .controls= f.number_field :projects_limit
- .clearfix
+ .control-group
= f.label :can_create_group
- .input= f.check_box :can_create_group
-
- .clearfix
- = f.label :can_create_team
- .input= f.check_box :can_create_team
+ .controls= f.check_box :can_create_group
- .clearfix
+ .control-group
= f.label :admin do
%strong.cred Administrator
- .input= f.check_box :admin
+ .controls= f.check_box :admin
.span4
- - unless @admin_user.new_record?
+ - unless @user.new_record?
.alert.alert-error
- - if @admin_user.blocked
+ - if @user.blocked?
%p This user is blocked and is not able to login to GitLab
- = link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn btn-small"
+ = link_to 'Unblock User', unblock_admin_user_path(@user), method: :put, class: "btn btn-small"
- else
%p Blocked users will be removed from all projects &amp; will not be able to login to GitLab.
- = link_to 'Block User', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove"
+ = link_to 'Block User', block_admin_user_path(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove"
%fieldset
%legend Profile
- .clearfix
+ .control-group
= f.label :skype
- .input= f.text_field :skype
- .clearfix
+ .controls= f.text_field :skype
+ .control-group
= f.label :linkedin
- .input= f.text_field :linkedin
- .clearfix
+ .controls= f.text_field :linkedin
+ .control-group
= f.label :twitter
- .input= f.text_field :twitter
+ .controls= f.text_field :twitter
- .actions
- = f.submit 'Save', class: "btn btn-save"
- - if @admin_user.new_record?
+ .form-actions
+ - if @user.new_record?
+ = f.submit 'Create user', class: "btn btn-create"
= link_to 'Cancel', admin_users_path, class: "btn btn-cancel"
- else
- = link_to 'Cancel', admin_user_path(@admin_user), class: "btn btn-cancel"
+ = f.submit 'Save changes', class: "btn btn-save"
+ = link_to 'Cancel', admin_user_path(@user), class: "btn btn-cancel"
diff --git a/app/views/admin/users/edit.html.haml b/app/views/admin/users/edit.html.haml
index f8ff77b8f53..2a4f8c60546 100644
--- a/app/views/admin/users/edit.html.haml
+++ b/app/views/admin/users/edit.html.haml
@@ -1,5 +1,5 @@
-%h3.page_title
- #{@admin_user.name} &rarr;
+%h3.page-title
+ #{@user.name} &rarr;
%i.icon-edit
Edit user
%hr
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index f5bb8b0681d..b32f0ae87cc 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -1,8 +1,3 @@
-%h3.page_title
- Users
- = link_to 'New User', new_admin_user_path, class: "btn btn-small pull-right"
-%br
-
.row
.span3
.admin-filter
@@ -32,10 +27,12 @@
.span9
.ui-box
- %h5.title
- Users (#{@admin_users.total_count})
+ .title
+ Users (#{@users.total_count})
+ .pull-right
+ = link_to 'New User', new_admin_user_path, class: "btn btn-new"
%ul.well-list
- - @admin_users.each do |user|
+ - @users.each do |user|
%li
- if user.blocked?
%i.icon-lock.cred
@@ -53,10 +50,9 @@
&nbsp;
= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-small"
- unless user == current_user
- - if user.blocked
+ - if user.blocked?
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-small success"
- else
= link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove"
= link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-small btn-remove"
- %li.bottom
- = paginate @admin_users, theme: "gitlab"
+ = paginate @users, theme: "gitlab"
diff --git a/app/views/admin/users/new.html.haml b/app/views/admin/users/new.html.haml
index 1e82b249cf1..a1c90c48946 100644
--- a/app/views/admin/users/new.html.haml
+++ b/app/views/admin/users/new.html.haml
@@ -1,4 +1,4 @@
-%h3.page_title
+%h3.page-title
%i.icon-plus
New user
%hr
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index c5d60194820..3df9903e409 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -1,83 +1,137 @@
+%h3.page-title
+ User:
+ = @user.name
+ - if @user.blocked?
+ %span.cred (Blocked)
+ - if @user.admin
+ %span.cred (Admin)
+
+ .pull-right
+ = link_to edit_admin_user_path(@user), class: "btn grouped" do
+ %i.icon-edit
+ Edit
+ - if @user.blocked?
+ = link_to 'Unblock', unblock_admin_user_path(@user), method: :put, class: "btn grouped success"
+%hr
+
.row
.span6
- %h3.page_title
- = image_tag gravatar_icon(@admin_user.email, 90), class: "avatar s90"
- = @admin_user.name
- - if @admin_user.blocked
- %span.cred (Blocked)
- - if @admin_user.admin
- %span.cred (Admin)
- .pull-right
- = link_to edit_admin_user_path(@admin_user), class: "btn pull-right" do
- %i.icon-edit
- Edit
- %br
- %small @#{@admin_user.username}
- %br
- %small member since #{@admin_user.created_at.stamp("Nov 12, 2031")}
- .clearfix
- %hr
- %h5
- Add User to Projects
- %small
- Read more about project permissions
- %strong= link_to "here", help_permissions_path, class: "vlink"
- %br
- = form_tag team_update_admin_user_path(@admin_user), class: "bulk_import", method: :put do
- .control-group
- = label_tag :project_ids, "Projects", class: 'control-label'
- .controls
- = select_tag :project_ids, options_from_collection_for_select(@not_in_projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span3'
- .control-group
- = label_tag :project_access, "Project Access", class: 'control-label'
- .controls
- = select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select chosen span3"
-
- .form-actions
- = submit_tag 'Add', class: "btn btn-create"
+ .ui-box
+ .title
+ Account:
.pull-right
- %br
+ = image_tag gravatar_icon(@user.email, 32), class: "avatar s32"
+ %ul.well-list
+ %li
+ %span.light Name:
+ %strong= @user.name
+ %li
+ %span.light Username:
+ %strong
+ = @user.username
+ %li
+ %span.light Email:
+ %strong
+ = mail_to @user.email
+ %li
+ %span.light Can create groups:
+ %strong
+ = @user.can_create_group ? "Yes" : "No"
+ %li
+ %span.light Personal projects limit:
+ %strong
+ = @user.projects_limit
+ %li
+ %span.light Member since:
+ %strong
+ = @user.created_at.stamp("Nov 12, 2031")
- - if @admin_user.owned_groups.present?
- .ui-box
- %h5.title Owned groups:
- %ul.well-list
- - @admin_user.groups.each do |group|
+ %li
+ %span.light Last sign-in at:
+ %strong
+ - if @user.last_sign_in_at
+ = @user.last_sign_in_at.stamp("Nov 12, 2031")
+ - else
+ never
+
+ - if @user.ldap_user?
+ %li
+ %span.light LDAP uid:
+ %strong
+ = @user.extern_uid
+
+ - if @user.created_by
+ %li
+ %span.light Created by:
+ %strong
+ = link_to @user.created_by.name, [:admin, @user.created_by]
+
+ - unless @user == current_user
+ .alert
+ %h4 Block user
+ %br
+ %p Blocking user has the following effects:
+ %ul
+ %li User will not be able to login
+ %li User will not be able to access git repositories
+ %li User will be removed from joined projects and groups
+ %li Personal projects will be left
+ %li Owned groups will be left
+ = link_to 'Block user', block_admin_user_path(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-remove"
+
+ .alert.alert-error
+ %h4
+ Remove user
+ %br
+ %p Deleting a user has the following effects:
+ %ul
+ %li All user content like authored issues, snippets, comments will be removed
+ - rp = @user.personal_projects.count
+ - unless rp.zero?
+ %li #{pluralize rp, 'personal project'} will be removed and cannot be restored
+ - if @user.solo_owned_groups.present?
%li
- %strong= link_to group.name, admin_group_path(group)
+ Next groups with all content will be removed:
+ %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
+ = link_to 'Remove user', [:admin, @user], confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-remove"
- - if @admin_user.owned_teams.present?
+ .span6
+ - if @user.users_groups.present?
.ui-box
- %h5.title Owned teams:
+ .title Groups:
%ul.well-list
- - @admin_user.owned_teams.each do |team|
- %li
- %strong= link_to team.name, admin_team_path(team)
-
+ - @user.users_groups.each do |user_group|
+ - group = user_group.group
+ %li.users_group
+ %strong= link_to group.name, admin_group_path(group)
+ .pull-right
+ %span.light= user_group.human_access
+ - unless user_group.owner?
+ = link_to group_users_group_path(group, user_group), 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-remove.icon-white
- .span6
- = render 'users/profile', user: @admin_user
.ui-box
- %h5.title Projects (#{@projects.count})
+ .title Projects (#{@projects.count})
%ul.well-list
- - @projects.each do |project|
- %li
+ - @projects.sort_by(&:name_with_namespace).each do |project|
+ - tm = project.team.find_tm(@user.id)
+ %li.users_project
= link_to admin_project_path(project), class: dom_class(project) do
- if project.namespace
= project.namespace.human_name
\/
%strong.well-title
= truncate(project.name, length: 45)
- %span.pull-right.light
- - if project.owner == @admin_user
- %i.icon-wrench
- - tm = project.team.get_tm(@admin_user.id)
- - if tm
- = tm.project_access_human
- = link_to edit_admin_project_member_path(project, tm.user), class: "btn btn-small" do
- %i.icon-edit
- = link_to admin_project_member_path(project, tm.user), confirm: 'Are you sure?', method: :delete, class: "btn btn-small btn-remove" do
- %i.icon-remove
- %p.light
- %i.icon-wrench
- &ndash; user is a project owner
+
+ - if tm
+ .pull-right
+ - if tm.owner?
+ %span.light Owner
+ - else
+ %span.light= tm.human_access
+
+ - if tm.respond_to? :project
+ = link_to project_team_member_path(project, @user), confirm: remove_from_project_team_message(project, @user), remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do
+ %i.icon-remove
+
+
diff --git a/app/views/blame/_head.html.haml b/app/views/blame/_head.html.haml
deleted file mode 100644
index ef9e6c9c532..00000000000
--- a/app/views/blame/_head.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-%ul.nav.nav-tabs
- %li
- = render partial: 'shared/ref_switcher', locals: {destination: 'tree', path: params[:path]}
- = nav_link(controller: :refs) do
- = link_to 'Source', project_tree_path(@project, @ref)
- %li.pull-right
- = render "shared/clone_panel"
diff --git a/app/views/commit/show.html.haml b/app/views/commit/show.html.haml
deleted file mode 100644
index 485f2d1e67c..00000000000
--- a/app/views/commit/show.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-= render "commits/commit_box"
-
-%p.pull-right.cgray
- This commit has
- %span.cgreen #{@commit.stats.additions} additions
- and
- %span.cred #{@commit.stats.deletions} deletions
-
-= render "commits/diffs", diffs: @commit.diffs
-= render "notes/notes_with_form"
-
-:javascript
- $(function(){
- $('.files .file').each(function(){
- new CommitFile(this);
- });
- });
diff --git a/app/views/commits/_commit.html.haml b/app/views/commits/_commit.html.haml
deleted file mode 100644
index eb0312d01e1..00000000000
--- a/app/views/commits/_commit.html.haml
+++ /dev/null
@@ -1,21 +0,0 @@
-%li.commit
- .browse_code_link_holder
- %p
- %strong= link_to "Browse Code »", project_tree_path(@project, commit), class: "right"
- %p
- = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
- = commit.author_link avatar: true, size: 24
- &nbsp;
- = link_to_gfm truncate(commit.title, length: 50), project_commit_path(@project, commit.id), class: "row_title"
-
- %span.committed_ago
- = time_ago_in_words(commit.committed_date)
- ago
- &nbsp;
-
- %span.notes_count
- - notes = @project.notes.for_commit_id(commit.id)
- - if notes.any?
- %span.btn.disabled.grouped
- %i.icon-comment
- = notes.count
diff --git a/app/views/commits/_commit_box.html.haml b/app/views/commits/_commit_box.html.haml
deleted file mode 100644
index 4c80c13ced1..00000000000
--- a/app/views/commits/_commit_box.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-.ui-box.ui-box-show
- .ui-box-head
- .pull-right
- - if @notes_count > 0
- %span.btn.disabled.grouped
- %i.icon-comment
- = @notes_count
- .left.btn-group
- %a.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
- %span Browse Code »
- %h3.commit-title.page_title
- = gfm escape_once(@commit.title)
- - if @commit.description.present?
- %pre.commit-description
- = gfm escape_once(@commit.description)
- .ui-box-body
- .row
- .span5
- .author
- = @commit.author_link avatar: true, size: 32
- authored
- %time{title: @commit.authored_date.stamp("Aug 21, 2011 9:23pm")}
- #{time_ago_in_words(@commit.authored_date)} ago
- - if @commit.different_committer?
- .committer
- &rarr;
- = @commit.committer_link
- committed
- %time{title: @commit.committed_date.stamp("Aug 21, 2011 9:23pm")}
- #{time_ago_in_words(@commit.committed_date)} ago
- .span6.pull-right
- .pull-right
- .sha-block
- %span.cgray commit
- %span.label_commit= @commit.id
- .clearfix
- .pull-right
- .sha-block
- %span.cgray= pluralize(@commit.parents.count, "parent")
- - @commit.parents.each do |parent|
- = link_to parent.id[0...10], project_commit_path(@project, parent)
-
-
diff --git a/app/views/commits/_commits.html.haml b/app/views/commits/_commits.html.haml
deleted file mode 100644
index 191320933d3..00000000000
--- a/app/views/commits/_commits.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-- @commits.group_by { |c| c.committed_date.to_date }.each do |day, commits|
- %div.ui-box
- %h5.title
- %i.icon-calendar
- %span= day.stamp("28 Aug, 2010")
- %ul.well-list= render commits
diff --git a/app/views/commits/_diffs.html.haml b/app/views/commits/_diffs.html.haml
deleted file mode 100644
index 76f9f267b41..00000000000
--- a/app/views/commits/_diffs.html.haml
+++ /dev/null
@@ -1,49 +0,0 @@
-- if @suppress_diff
- .alert.alert-block
- %p
- %strong Warning! Large commit with more than #{Commit::DIFF_SAFE_SIZE} files changed.
- %p To prevent performance issue we rejected diff information.
- %p
- But if you still want to see diff
- = link_to "click this link", project_commit_path(@project, @commit, force_show_diff: true), class: "underlined_link"
-
-%p.cgray
- Showing #{pluralize(diffs.count, "changed file")}
-.file-stats
- = render "commits/diff_head", diffs: diffs
-
-.files
- - unless @suppress_diff
- - diffs.each_with_index do |diff, i|
- - next if diff.diff.empty?
- - file = (@commit.tree / diff.new_path)
- - file = (@commit.prev_commit.tree / diff.old_path) unless file
- - next unless file
- .file{id: "diff-#{i}"}
- .header
- - if diff.deleted_file
- %span= diff.old_path
-
- - if @commit.prev_commit
- = link_to project_tree_path(@project, tree_join(@commit.prev_commit_id, diff.new_path)), {:class => 'btn pull-right 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_tree_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do
- View file @
- %span.commit-short-id= @commit.short_id(6)
-
- .content
- -# Skipp all non non-supported blobs
- - next unless file.respond_to?('text?')
- - if file.text?
- = render "commits/text_file", diff: diff, index: i
- - elsif file.image?
- - old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil?
- = render "commits/image", diff: diff, old_file: old_file, file: file, index: i
- - else
- %p.nothing_here_message No preview for this file type
diff --git a/app/views/compare/_form.html.haml b/app/views/compare/_form.html.haml
deleted file mode 100644
index 7c0688a2287..00000000000
--- a/app/views/compare/_form.html.haml
+++ /dev/null
@@ -1,39 +0,0 @@
-%div
- - unless params[:to]
- %p.slead
- Fill input field with commit id like
- %code.label_branch 4eedf23
- or branch/tag name like
- %code.label_branch master
- and press compare button for commits list, code diff.
-
- %br
-
- = form_tag project_compare_index_path(@project), method: :post do
- .clearfix
- .pull-left
- - if params[:to] && params[:from]
- = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'}
- = text_field_tag :from, params[:from], placeholder: "master", class: "xlarge"
- = "..."
- = text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge"
- .pull-left
- &nbsp;
- = submit_tag "Compare", class: "btn btn-primary wide commits-compare-btn"
- - if @refs_are_same
- .alert
- %span Refs are the same
-
-
-
-:javascript
- $(function() {
- var availableTags = #{@project.repository.ref_names.to_json};
-
- $("#from, #to").autocomplete({
- source: availableTags,
- minLength: 1
- });
-
- disableButtonIfEmptyField('#to', '.commits-compare-btn');
- });
diff --git a/app/views/compare/index.html.haml b/app/views/compare/index.html.haml
deleted file mode 100644
index 6c9a5fd8305..00000000000
--- a/app/views/compare/index.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-= render "commits/head"
-
-%h3.page_title
- Compare View
-%hr
-
-= render "form"
diff --git a/app/views/compare/show.html.haml b/app/views/compare/show.html.haml
deleted file mode 100644
index d8ea3727d57..00000000000
--- a/app/views/compare/show.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-= render "commits/head"
-
-%h3.page_title
- Compare View
-%hr
-
-= render "form"
-
-- if @commits.present?
- %div.ui-box
- %h5.title
- Commits (#{@commits.count})
- %ul.well-list= render @commits
-
- - unless @diffs.empty?
- %h4 Diff
- = render "commits/diffs", diffs: @diffs
diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index 2b7d23c225d..89117726317 100644
--- a/app/views/dashboard/_activities.html.haml
+++ b/app/views/dashboard/_activities.html.haml
@@ -1,10 +1,5 @@
= render "events/event_last_push", event: @last_push
-
-.event_filter
- = event_filter_link EventFilter.push, 'Push events'
- = event_filter_link EventFilter.merged, 'Merge events'
- = event_filter_link EventFilter.comments, 'Comments'
- = event_filter_link EventFilter.team, 'Team'
+= render 'shared/event_filter'
- if @events.any?
.content_list
diff --git a/app/views/dashboard/_filter.html.haml b/app/views/dashboard/_filter.html.haml
deleted file mode 100644
index 82e679d5927..00000000000
--- a/app/views/dashboard/_filter.html.haml
+++ /dev/null
@@ -1,33 +0,0 @@
-= form_tag dashboard_filter_path(entity), method: 'get' do
- %fieldset.dashboard-search-filter
- = search_field_tag "search", params[:search], { placeholder: 'Search', class: 'search-text-input' }
- = button_tag type: 'submit', class: 'btn' do
- %i.icon-search
-
- %fieldset
- %legend Status:
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if !params[:status])}
- = link_to dashboard_filter_path(entity, status: nil) do
- Open
- %li{class: ("active" if params[:status] == 'closed')}
- = link_to dashboard_filter_path(entity, status: 'closed') do
- Closed
- %li{class: ("active" if params[:status] == 'all')}
- = link_to dashboard_filter_path(entity, status: 'all') do
- All
-
- %fieldset
- %legend Projects:
- %ul.nav.nav-pills.nav-stacked
- - @projects.each do |project|
- - unless entities_per_project(project, entity).zero?
- %li{class: ("active" if params[:project_id] == project.id.to_s)}
- = link_to dashboard_filter_path(entity, project_id: project.id) do
- = project.name_with_namespace
- %small.pull-right= entities_per_project(project, entity)
-
- %fieldset
- %hr
- = link_to "Reset", dashboard_filter_path(entity), class: 'btn pull-right'
-
diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml
index ba8d3029eaf..b4f3866228d 100644
--- a/app/views/dashboard/_groups.html.haml
+++ b/app/views/dashboard/_groups.html.haml
@@ -1,18 +1,19 @@
.ui-box
- %h5.title
- Groups
- %small
- (#{groups.count})
+ .title.clearfix
+ = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter'
- if current_user.can_create_group?
%span.pull-right
- = link_to new_group_path, class: "btn btn-tiny info" do
+ = link_to new_group_path, class: "btn btn-new" do
%i.icon-plus
- New Group
- %ul.well-list
+ New group
+ %ul.well-list.dash-list
- groups.each do |group|
- %li
+ %li.group-row
= link_to group_path(id: group.path), class: dom_class(group) do
- %strong.well-title= truncate(group.name, length: 35)
- %span.pull-right.light
- - if group.owner == current_user
- %i.icon-wrench
+ %span.group-name.filter-title
+ = truncate(group.name, length: 35)
+ %span.arrow
+ %i.icon-angle-right
+ - if groups.blank?
+ %li
+ %h3.nothing_here_message You have no groups yet.
diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml
new file mode 100644
index 00000000000..50b833f3d82
--- /dev/null
+++ b/app/views/dashboard/_project.html.haml
@@ -0,0 +1,12 @@
+= link_to project_path(project), class: dom_class(project) do
+ %span.namespace-name
+ - if project.namespace
+ = project.namespace.human_name
+ \/
+ %span.project-name.filter-title
+ = truncate(project.name, length: 25)
+ %span.arrow
+ %i.icon-angle-right
+ %span.last-activity
+ %span Last activity:
+ %span.date= project_last_activity(project)
diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml
index 30fb7268014..b79b27fc95a 100644
--- a/app/views/dashboard/_projects.html.haml
+++ b/app/views/dashboard/_projects.html.haml
@@ -1,31 +1,25 @@
-.projects_box
- %h5.title
- Projects
- %small
- (#{@projects_count})
+.ui-box
+ .title.clearfix
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter'
- if current_user.can_create_project?
%span.pull-right
- = link_to new_project_path, class: "btn btn-tiny info" do
+ = link_to new_project_path, class: "btn btn-new" do
%i.icon-plus
- New Project
+ New project
- %ul.well-list
+ %ul.well-list.dash-list
- projects.each do |project|
- %li
- = link_to project_path(project), class: dom_class(project) do
- - if project.namespace
- = project.namespace.human_name
- \/
- %strong.well-title
- = truncate(project.name, length: 25)
- %span.arrow
- &rarr;
- %span.last_activity
- %strong Last activity:
- %span= project_last_activity(project)
+ %li.project-row
+ = render "project", project: project
+
- if projects.blank?
%li
%h3.nothing_here_message There are no projects here.
- - if @projects_count > 20
+ - if @projects_count > @projects_limit
%li.bottom
- %strong= link_to "show all projects", projects_dashboard_path
+ %span.light
+ #{@projects_limit} of #{pluralize(@projects_count, 'project')} displayed.
+ .pull-right.append-right-10
+ = link_to projects_dashboard_path do
+ Show all
+ %i.icon-angle-right
diff --git a/app/views/dashboard/_sidebar.html.haml b/app/views/dashboard/_sidebar.html.haml
index 7c6daf6ec31..fed4b2776ae 100644
--- a/app/views/dashboard/_sidebar.html.haml
+++ b/app/views/dashboard/_sidebar.html.haml
@@ -1,16 +1,25 @@
-- if @teams.present?
- = render "teams", teams: @teams
-- if @groups.present?
- = render "groups", groups: @groups
-= render "projects", projects: @projects
-%div
+%ul.nav.nav-tabs.dash-sidebar-tabs
+ %li.active
+ = link_to '#projects', 'data-toggle' => 'tab', id: 'sidebar-projects-tab' do
+ Projects
+ %span.badge= @projects_count
+ %li
+ = link_to '#groups', 'data-toggle' => 'tab', id: 'sidebar-groups-tab' do
+ Groups
+ %span.badge= @groups.count
+
+.tab-content
+ .tab-pane.active#projects
+ = render "projects", projects: @projects
+ .tab-pane#groups
+ = render "groups", groups: @groups
+
+.prepend-top-20
%span.rss-icon
= link_to dashboard_path(:atom, { private_token: current_user.private_token }) do
- = image_tag "rss_ui.png", title: "feed"
- %strong News Feed
+ %strong
+ %i.icon-rss
+ News Feed
%hr
-.gitlab-promo
- = link_to "Homepage", "http://gitlab.org"
- = link_to "Blog", "http://blog.gitlab.org"
- = link_to "@gitlabhq", "https://twitter.com/gitlabhq"
+= render 'shared/promo'
diff --git a/app/views/dashboard/_teams.html.haml b/app/views/dashboard/_teams.html.haml
deleted file mode 100644
index f56115856a7..00000000000
--- a/app/views/dashboard/_teams.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-.ui-box.teams-box
- %h5.title
- Teams
- %small
- (#{@teams.count})
- %span.pull-right
- = link_to new_team_path, class: "btn btn-tiny info" do
- %i.icon-plus
- New Team
- %ul.well-list
- - @teams.each do |team|
- %li
- = link_to team_path(id: team.path), class: dom_class(team) do
- %strong.well-title= truncate(team.name, length: 35)
- %span.pull-right.light
- - if team.owner == current_user
- %i.icon-wrench
- - tm = current_user.user_team_user_relationships.find_by_user_team_id(team.id)
- - if tm
- = tm.access_human
diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml
index 4b0d0d6873d..79d5dca8845 100644
--- a/app/views/dashboard/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/_zero_authorized_projects.html.haml
@@ -1,12 +1,36 @@
-%h3.nothing_here_message
- There are no projects you have access to.
- %br
- - if current_user.can_create_project?
- You can create up to
- = current_user.projects_limit
- projects. Click on button below to add a new one
- .link_holder
- = link_to new_project_path, class: "btn btn-primary" do
- New Project »
- - else
- If you will be added to project - it will be displayed here
+%h3.page-title Welcome to GitLab!
+%p.light Self Hosted Git Management application.
+%hr
+%div
+ .dashboard-intro-icon
+ %i.icon-bookmark-empty
+ %div
+ %p.slead
+ You don't have access to any projects right now.
+ %br
+ - if current_user.can_create_project?
+ You can create up to
+ %strong= pluralize(current_user.projects_limit, "project") + "."
+ Click on the button below to add a new one
+ - else
+ If you are added to a project, it will be displayed here
+
+ - if current_user.can_create_project?
+ .link_holder
+ = link_to new_project_path, class: "btn btn-new" do
+ New project »
+
+- if current_user.can_create_group?
+ %hr
+ %div
+ .dashboard-intro-icon
+ %i.icon-group
+ %div
+ %p.slead
+ You can create a group for several dependent projects.
+ %br
+ Group is the best way to manage projects and members
+ .link_holder
+ = link_to new_group_path, class: "btn btn-new" do
+ New group »
+
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index affe01a7ef9..82880d5ef63 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -1,24 +1,13 @@
-%h3.page_title
- Issues
- %small (assigned to you)
- %small.pull-right #{@issues.total_count} issues
+%h3.page-title
+ Issues assigned to me
+ %span.pull-right #{@issues.total_count} issues
+%p.light
+ For all issues you should visit the project's issues page, or use the search panel to find a specific issue.
%hr
.row
.span3
- = render 'filter', entity: 'issue'
+ = render 'shared/filter', entity: 'issue'
.span9
- - if @issues.any?
- - @issues.group_by(&:project).each do |group|
- %div.ui-box
- - project = group[0]
- %h5.title
- = link_to_project project
- %ul.well-list.issues_table
- - group[1].each do |issue|
- = render(partial: 'issues/show', locals: {issue: issue})
- %hr
- = paginate @issues, theme: "gitlab"
- - else
- %p.nothing_here_message Nothing to show here
+ = render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index a311729dd4d..9c96edeefd5 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -1,11 +1,13 @@
-%h3.page_title
+%h3.page-title
Merge Requests
- %small (authored by or assigned to you)
- %small.pull-right #{@merge_requests.total_count} merge requests
+ %span.pull-right #{@merge_requests.total_count} merge requests
+
+%p.light
+ Only merge requests created by you or assigned to you are listed here.
%hr
.row
.span3
- = render 'filter', entity: 'merge_request'
+ = render 'shared/filter', entity: 'merge_request'
.span9
= render 'shared/merge_requests'
diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml
index 8e21b0c7e02..0dcb1a87e9a 100644
--- a/app/views/dashboard/projects.html.haml
+++ b/app/views/dashboard/projects.html.haml
@@ -1,56 +1,89 @@
-%h3.page_title
- Projects
- %span
- (#{@projects.total_count})
- - if current_user.can_create_project?
- %span.pull-right
- = link_to new_project_path, class: "btn btn-tiny info" do
- %i.icon-plus
- New Project
-
-
+%h3.page-title My Projects
+%p.light
+ All projects you have access to are listed here. Public projects are not included here unless you are a member
%hr
.row
.span3
%ul.nav.nav-pills.nav-stacked
= nav_tab :scope, nil do
- = link_to "All", projects_dashboard_path
+ = link_to projects_dashboard_path do
+ All
+ %span.pull-right
+ = current_user.authorized_projects.count
= nav_tab :scope, 'personal' do
- = link_to "Personal", projects_dashboard_path(scope: 'personal')
+ = link_to projects_dashboard_path(scope: 'personal') do
+ Personal
+ %span.pull-right
+ = current_user.personal_projects.count
= nav_tab :scope, 'joined' do
- = link_to "Joined", projects_dashboard_path(scope: 'joined')
+ = link_to projects_dashboard_path(scope: 'joined') do
+ Joined
+ %span.pull-right
+ = current_user.authorized_projects.joined(current_user).count
+ = nav_tab :scope, 'owned' do
+ = link_to projects_dashboard_path(scope: 'owned') do
+ Owned
+ %span.pull-right
+ = current_user.owned_projects.count
+
+
+ - if @groups.present?
+ %fieldset
+ %legend Groups
+ %ul.bordered-list
+ - @groups.each do |group|
+ %li{ class: (group.name == params[:group]) ? 'active' : 'light' }
+ = link_to projects_dashboard_path(group: group.name) do
+ %i.icon-folder-close-alt
+ = group.name
+ %small.pull-right
+ = group.projects.count
+
+
+
+ - if @labels.present?
+ %fieldset
+ %legend Labels
+ %ul.bordered-list
+ - @labels.each do |label|
+ %li{ class: (label.name == params[:label]) ? 'active' : 'light' }
+ = link_to projects_dashboard_path(scope: params[:scope], label: label.name) do
+ %i.icon-tag
+ = label.name
.span9
- = form_tag projects_dashboard_path, method: 'get' do
- %fieldset.dashboard-search-filter
- = hidden_field_tag "scope", params[:scope]
- = search_field_tag "search", params[:search], { placeholder: 'Search', class: 'left input-xxlarge' }
- = button_tag type: 'submit', class: 'btn' do
- %i.icon-search
-
- %ul.well-list
+ %ul.bordered-list.my-projects.top-list
- @projects.each do |project|
- %li.clearfix
- .left
- = link_to project_path(project), class: dom_class(project) do
- - if project.namespace
- = project.namespace.human_name
- \/
- %strong.well-title
- = truncate(project.name, length: 25)
- %br
- %small.light
- %strong Last activity:
- %span= project_last_activity(project)
- .pull-right.light
- - if project.owner == current_user
- %i.icon-wrench
- - tm = project.team.get_tm(current_user.id)
- - if tm
- = tm.project_access_human
-
- - if @projects.blank?
%li
- %h3.nothing_here_message There are no projects here.
- .bottom= paginate @projects, theme: "gitlab"
+ %h4.project-title
+ %span.access-icon
+ - if project.public
+ = public_icon
+ - else
+ = private_icon
+ = link_to project_path(project), class: dom_class(project) do
+ %strong= project.name_with_namespace
+
+ - if project.forked_from_project
+ %small.pull-right
+ %i.icon-code-fork
+ Forked from:
+ = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project)
+ .project-info
+ .pull-right
+ - project.labels.each do |label|
+ %span.label.label-info
+ %i.icon-tag
+ = label.name
+ - if project.description.present?
+ %p= truncate project.description, length: 100
+ .last-activity
+ %span.light Last activity:
+ %span.date= project_last_activity(project)
+
+ - if @projects.blank?
+ %li
+ %h3.nothing_here_message There are no projects here.
+ .bottom
+ = paginate @projects, theme: "gitlab"
diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder
index 2bb42a65bac..a913df92299 100644
--- a/app/views/dashboard/show.atom.builder
+++ b/app/views/dashboard/show.atom.builder
@@ -1,18 +1,17 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}"
- xml.link :href => projects_url(:atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => projects_url, :rel => "alternate", :type => "text/html"
+ xml.link :href => dashboard_url(:atom), :rel => "self", :type => "application/atom+xml"
+ xml.link :href => dashboard_url, :rel => "alternate", :type => "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
if event.proper?
- event = EventDecorator.decorate(event)
xml.entry do
- event_link = event.feed_url
- event_title = event.feed_title
- event_summary = event.feed_summary
+ event_link = event_feed_url(event)
+ event_title = event_feed_title(event)
+ event_summary = event_feed_summary(event)
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link :href => event_link
diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml
index 1a66ba4fb37..2305eae1f71 100644
--- a/app/views/dashboard/show.html.haml
+++ b/app/views/dashboard/show.html.haml
@@ -1,5 +1,5 @@
- if @has_authorized_projects
- .projects
+ .dashboard
.activities.span8
= render 'activities'
.side.span4
@@ -7,6 +7,3 @@
- else
= render "zero_authorized_projects"
-
-:javascript
- dashboardPage();
diff --git a/app/views/deploy_keys/_show.html.haml b/app/views/deploy_keys/_show.html.haml
deleted file mode 100644
index 635054350ec..00000000000
--- a/app/views/deploy_keys/_show.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-%tr
- %td
- %a{href: project_deploy_key_path(key.project, key)}
- %strong= key.title
- %td
- %span.update-author
- Added
- = time_ago_in_words(key.created_at)
- ago
- %td
- = link_to 'Remove', project_deploy_key_path(key.project, key), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
-
diff --git a/app/views/deploy_keys/index.html.haml b/app/views/deploy_keys/index.html.haml
deleted file mode 100644
index db167f4e2f2..00000000000
--- a/app/views/deploy_keys/index.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-= render "repositories/head"
-
-%p.slead
- Deploy keys allow read-only access to repository. It matches perfectly for CI, staging or production servers.
-
- - if can? current_user, :admin_project, @project
- = link_to new_project_deploy_key_path(@project), class: "btn btn-small", title: "New Deploy Key" do
- Add Deploy Key
-- if @keys.any?
- %table
- %thead
- %tr
- %th Keys
- %th
- %th
- - @keys.each do |key|
- = render(partial: 'show', locals: {key: key})
diff --git a/app/views/deploy_keys/new.html.haml b/app/views/deploy_keys/new.html.haml
deleted file mode 100644
index e973cb7d305..00000000000
--- a/app/views/deploy_keys/new.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-= render "repositories/head"
-
-%h3.page_title New Deploy key
-%hr
-
-= render 'form'
diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb
index 5399a961570..adc9b672092 100644
--- a/app/views/devise/confirmations/new.html.erb
+++ b/app/views/devise/confirmations/new.html.erb
@@ -1,6 +1,6 @@
<h2>Resend confirmation instructions</h2>
-<%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %>
+<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
@@ -9,4 +9,4 @@
<div><%= f.submit "Resend confirmation instructions" %></div>
<% end %>
-<%= render :partial => "devise/shared/links" %>
+<%= render partial: "devise/shared/links" %>
diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb
index a6ea8ca17e8..7b4fd526964 100644
--- a/app/views/devise/mailer/confirmation_instructions.html.erb
+++ b/app/views/devise/mailer/confirmation_instructions.html.erb
@@ -2,4 +2,4 @@
<p>You can confirm your account through the link below:</p>
-<p><%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %></p>
+<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @resource.confirmation_token) %></p>
diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb
index ae9e888abb9..e1144e943b4 100644
--- a/app/views/devise/mailer/reset_password_instructions.html.erb
+++ b/app/views/devise/mailer/reset_password_instructions.html.erb
@@ -2,7 +2,7 @@
<p>Someone has requested a link to change your password, and you can do this through the link below.</p>
-<p><%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %></p>
+<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @resource.reset_password_token) %></p>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb
index 2263c219522..0429883f05b 100644
--- a/app/views/devise/mailer/unlock_instructions.html.erb
+++ b/app/views/devise/mailer/unlock_instructions.html.erb
@@ -4,4 +4,4 @@
<p>Click the link below to unlock your account:</p>
-<p><%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %></p>
+<p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @resource.unlock_token) %></p>
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index e5800025c6d..d85b4ab08b2 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -1,5 +1,4 @@
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: "login-box" }) do |f|
- = image_tag "login-logo.png", width: "304", height: "66", class: "login-logo", alt: "Login Logo"
%h3 Change your password
= devise_error_messages!
= f.hidden_field :reset_password_token
diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb
deleted file mode 100644
index 0e39f318726..00000000000
--- a/app/views/devise/passwords/new.html.erb
+++ /dev/null
@@ -1,9 +0,0 @@
-<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { class: "login-box", method: :post }) do |f| %>
- <%= image_tag "login-logo.png", width: "304", height: "66", class: "login-logo", alt: "Login Logo" %>
- <%= devise_error_messages! %>
- <%= f.email_field :email, placeholder: "Email", class: "text" %>
- <br/>
- <br/>
- <%= f.submit "Reset password", class: "btn-primary btn" %>
- <div class="pull-right"> <%= link_to "Sign in", new_session_path(resource_name), class: "btn" %><br /></div>
-<% end %>
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
new file mode 100644
index 00000000000..3df65d037af
--- /dev/null
+++ b/app/views/devise/passwords/new.html.haml
@@ -0,0 +1,10 @@
+= form_for(resource, as: resource_name, url: password_path(resource_name), html: { class: "login-box", method: :post }) do |f|
+ %h3.page-title Reset password
+ = devise_error_messages!
+ = f.email_field :email, placeholder: "Email", class: "text"
+ %br/
+ %br/
+ = f.submit "Reset password", class: "btn-primary btn"
+ .pull-right
+ = link_to "Sign in", new_session_path(resource_name), class: "btn"
+ %br/
diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb
index dd26e8a47b8..139acf28a9f 100644
--- a/app/views/devise/registrations/edit.html.erb
+++ b/app/views/devise/registrations/edit.html.erb
@@ -1,6 +1,6 @@
<h2>Edit <%= resource_name.to_s.humanize %></h2>
-<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
+<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
@@ -18,11 +18,11 @@
<div><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password %></div>
-<div><%= f.submit "Update", :class => "input_button" %></div>
+<div><%= f.submit "Update", class: "input_button" %></div>
<% end %>
<h3>Cancel my account</h3>
-<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.</p>
+<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), confirm: "Are you sure?", method: :delete %>.</p>
<%= link_to "Back", :back %>
diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml
index 12b0438229b..d749d7bac09 100644
--- a/app/views/devise/registrations/new.html.haml
+++ b/app/views/devise/registrations/new.html.haml
@@ -1,5 +1,6 @@
= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: "login-box" }) do |f|
- = image_tag "login-logo.png", width: "304", height: "66", class: "login-logo", alt: "Login Logo"
+ %h3.page-title Sign Up
+ %br
= devise_error_messages!
%div
= f.text_field :name, class: "text top", placeholder: "Name", required: true
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
new file mode 100644
index 00000000000..0c8be9d5c48
--- /dev/null
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -0,0 +1,14 @@
+= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
+ = f.text_field :login, class: "text top", placeholder: "Username or Email", autofocus: "autofocus"
+ = f.password_field :password, class: "text bottom", placeholder: "Password"
+ - if devise_mapping.rememberable?
+ .clearfix.append-bottom-10
+ %label.checkbox.remember_me{for: "user_remember_me"}
+ = f.check_box :remember_me
+ %span Remember me
+ %div
+ = f.submit "Sign in", class: "btn-create btn"
+ .pull-right
+ = link_to "Forgot your password?", new_password_path(resource_name), class: "btn"
+
+
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 7968b0e9c9f..575d33949b6 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,29 +1,5 @@
-= form_tag(user_omniauth_callback_path(:ldap), :class => "login-box", :id => 'new_ldap_user' ) do
- = image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo"
- = text_field_tag :username, nil, {:class => "text top", :placeholder => "LDAP Login"}
- = password_field_tag :password, nil, {:class => "text bottom", :placeholder => "Password"}
+= form_tag(user_omniauth_callback_path(:ldap), id: 'new_ldap_user' ) do
+ = text_field_tag :username, nil, {class: "text top", placeholder: "LDAP Login", autofocus: "autofocus"}
+ = password_field_tag :password, nil, {class: "text bottom", placeholder: "Password"}
%br/
- = submit_tag "LDAP Sign in", :class => "btn-primary btn"
- - if devise_mapping.omniauthable?
- - (resource_class.omniauth_providers - [:ldap]).each do |provider|
- %hr/
- = link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider), :class => "btn btn-primary"
- %br/
- %hr/
- %a#other_form_toggle{:href => "#", :onclick => "javascript:$('#new_user').toggle();"} Other Sign in
- :javascript
- $(function() {
- $('#new_user').toggle();
- });
-= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
- = f.text_field :email, :class => "text top", :placeholder => "Email"
- = f.password_field :password, :class => "text bottom", :placeholder => "Password"
- - if devise_mapping.rememberable?
- .clearfix.inputs-list
- %label.checkbox.remember_me{:for => "user_remember_me"}
- = f.check_box :remember_me
- %span Remember me
- %br/
- = f.submit "Sign in", :class => "btn-primary btn"
- .pull-right
- = render :partial => "devise/shared/links"
+ = submit_tag "LDAP Sign in", class: "btn-create btn"
diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/sessions/_oauth_providers.html.haml
new file mode 100644
index 00000000000..935bc6af505
--- /dev/null
+++ b/app/views/devise/sessions/_oauth_providers.html.haml
@@ -0,0 +1,11 @@
+- providers = (enabled_oauth_providers - [:ldap])
+- if providers.present?
+ %hr
+ %div{:'data-no-turbolink' => 'data-no-turbolink'}
+ %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"
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index d904e701b8a..c6e1d8db577 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -1,28 +1,30 @@
-- if ldap_enable?
- = render :partial => 'devise/sessions/new_ldap'
-- else
- = form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
- = image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo"
- = f.email_field :email, :class => "text top", :placeholder => "Email", :autofocus => "autofocus"
- = f.password_field :password, :class => "text bottom", :placeholder => "Password"
- - if devise_mapping.rememberable?
- .clearfix.inputs-list
- %label.checkbox.remember_me{:for => "user_remember_me"}
- = f.check_box :remember_me
- %span Remember me
- %br/
- = f.submit "Sign in", :class => "btn-create btn"
- .pull-right
- = link_to "Forgot your password?", new_password_path(resource_name), :class => "btn"
- %br/
- - if Gitlab.config.gitlab.signup_enabled
- %hr/
+.login-box
+ %h3.page-title Sign in
+ - if ldap_enabled?
+ %ul.nav.nav-tabs
+ %li.active
+ = link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab'
+ %li
+ = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
+ .tab-content
+ %div#tab-ldap.tab-pane.active
+ = render partial: 'devise/sessions/new_ldap'
+ %div#tab-signin.tab-pane
+ = render partial: 'devise/sessions/new_base'
+
+ - else
+ = render partial: 'devise/sessions/new_base'
+
+
+ = render 'devise/sessions/oauth_providers' if devise_mapping.omniauthable?
+
+ - if Gitlab.config.gitlab.signup_enabled
+ %hr
+ %div
Don't have an account?
- = link_to "Sign up", new_registration_path(resource_name)
- - if devise_mapping.omniauthable? && resource_class.omniauth_providers.present?
- %hr
- %div
- %span Sign in with: &nbsp;
- - resource_class.omniauth_providers.each do |provider|
- %span
- = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider)
+ %strong
+ = link_to "Sign up", new_registration_path(resource_name)
+
+ - if extra_config.has_key?('sign_in_text')
+ %hr
+ = markdown(extra_config.sign_in_text)
diff --git a/app/views/devise/shared/_links.erb b/app/views/devise/shared/_links.erb
index d7499d14ec5..a47b5ff1ec7 100644
--- a/app/views/devise/shared/_links.erb
+++ b/app/views/devise/shared/_links.erb
@@ -1,5 +1,5 @@
<%- if controller_name != 'sessions' %>
- <%= link_to "Sign in", new_session_path(resource_name), :class => "btn" %><br />
+ <%= link_to "Sign in", new_session_path(resource_name), class: "btn" %><br />
<% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
@@ -7,7 +7,7 @@
<% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
-<%= link_to "Forgot your password?", new_password_path(resource_name), :class => "btn" %><br />
+<%= link_to "Forgot your password?", new_password_path(resource_name), class: "btn" %><br />
<% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb
index b787e648ca2..f9277d1673f 100644
--- a/app/views/devise/unlocks/new.html.erb
+++ b/app/views/devise/unlocks/new.html.erb
@@ -1,6 +1,6 @@
<h2>Resend unlock instructions</h2>
-<%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %>
+<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
@@ -9,4 +9,4 @@
<div><%= f.submit "Resend unlock instructions" %></div>
<% end %>
-<%= render :partial => "devise/shared/links" %>
+<%= render partial: "devise/shared/links" %>
diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml
index f2d082cb77d..6aa78f0c2a8 100644
--- a/app/views/errors/access_denied.html.haml
+++ b/app/views/errors/access_denied.html.haml
@@ -1,5 +1,5 @@
%h1.http_status_code 403
-%h3.page_title Access Denied
+%h3.page-title Access Denied
%hr
%p You are not allowed to access this page.
%p Read more about project permissions #{link_to "here", help_permissions_path, class: "vlink"}
diff --git a/app/views/errors/encoding.html.haml b/app/views/errors/encoding.html.haml
index a0aa6306489..7021f06dd7f 100644
--- a/app/views/errors/encoding.html.haml
+++ b/app/views/errors/encoding.html.haml
@@ -1,4 +1,4 @@
%h1.http_status_code 500
-%h3.page_title Encoding Error
+%h3.page-title Encoding Error
%hr
%p Page can't be loaded because of an encoding error.
diff --git a/app/views/errors/git_not_found.html.haml b/app/views/errors/git_not_found.html.haml
index 5c9c4953284..d8ed7773207 100644
--- a/app/views/errors/git_not_found.html.haml
+++ b/app/views/errors/git_not_found.html.haml
@@ -1,5 +1,5 @@
%h1.http_status_code 404
-%h3.page_title Git Resource Not found
+%h3.page-title Git Resource Not found
%hr
%p
Application can't get access to some branch or commit in your repository. It
diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml
index ee23d2197b4..4b97ddefc72 100644
--- a/app/views/errors/not_found.html.haml
+++ b/app/views/errors/not_found.html.haml
@@ -1,4 +1,4 @@
%h1.http_status_code 404
-%h3.page_title The resource you were looking for doesn't exist.
+%h3.page-title The resource you were looking for doesn't exist.
%hr
%p You may have mistyped the address or the page may have moved.
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index ea417aa9f30..4d4b24009f4 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -1,8 +1,5 @@
-- commit = CommitDecorator.decorate(commit)
%li.commit
- %p
- = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
- %span= commit.author_name
- &ndash;
- = image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16
- = gfm escape_once(truncate(commit.title, length: 50)) rescue "--broken encoding"
+ .commit-row-title
+ = link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id", alt: ''
+ &nbsp;
+ = gfm escape_once(truncate(commit[:message], length: 70)) rescue "--broken encoding"
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 719f6c3787f..b3543460d65 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -1,15 +1,15 @@
- if event.proper?
- %div.event-item
+ .event-item{class: "#{event.body? ? "event-block" : "event-inline" }"}
%span.cgray.pull-right
#{time_ago_in_words(event.created_at)} ago.
- = image_tag gravatar_icon(event.author_email), class: "avatar s24"
+ = cache event do
+ = image_tag gravatar_icon(event.author_email), class: "avatar s24", alt:''
- - if event.push?
- = render "events/event/push", event: event
- .clearfix
- - elsif event.note?
- = render "events/event/note", event: event
- - else
- = render "events/event/common", event: event
+ - if event.push?
+ = render "events/event/push", event: event
+ - elsif event.note?
+ = render "events/event/note", event: event
+ - else
+ = render "events/event/common", event: event
diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
index 2c2f270cf6c..de5634d3c55 100644
--- a/app/views/events/_event_last_push.html.haml
+++ b/app/views/events/_event_last_push.html.haml
@@ -1,5 +1,5 @@
- if show_last_push_widget?(event)
- .event_lp
+ .event-last-push
%span You pushed to
= link_to project_commits_path(event.project, event.ref_name) do
%strong= truncate(event.ref_name, length: 28)
@@ -8,6 +8,7 @@
%span
= time_ago_in_words(event.created_at)
ago.
-
- = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-new-mr" do
- Create Merge Request
+ .pull-right
+ = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-small" do
+ Create Merge Request
+ %hr
diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml
index d09e6e03f01..e44b366040f 100644
--- a/app/views/events/_event_push.atom.haml
+++ b/app/views/events/_event_push.atom.haml
@@ -1,12 +1,12 @@
-%div{:xmlns => "http://www.w3.org/1999/xhtml"}
+%div{xmlns: "http://www.w3.org/1999/xhtml"}
- event.commits.first(15).each do |commit|
%p
- %strong= commit.author_name
- = link_to "(##{commit.short_id})", project_commit_path(event.project, :id => commit.id)
+ %strong= commit[:author][:name]
+ = link_to "(##{commit[:id][0...8]})", project_commit_path(event.project, id: commit[:id])
%i
at
- = commit.committed_date.strftime("%Y-%m-%d %H:%M:%S")
- %blockquote= simple_format(escape_once(commit.safe_message))
+ = commit[:timestamp].to_time.to_s(:short)
+ %blockquote= simple_format(escape_once(commit[:message]))
- if event.commits_count > 15
%p
%i
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index 53cbe1c94ce..a9d3adf41df 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -2,11 +2,15 @@
%span.author_name= link_to_author event
%span.event_label{class: event.action_name}= event_action_name(event)
- if event.target
- %strong= link_to_gfm truncate(event.target_title), [event.project, event.target]
+ %strong= link_to "##{event.target_iid}", [event.project, event.target]
- else
- %strong= gfm truncate(event.target_title)
+ %strong= gfm event.target_title
at
- if event.project
= link_to_project event.project
- else
= event.project_name
+- if event.target.respond_to?(:title)
+ .event-body
+ .event-note
+ = event.target.title
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index 20c3b927067..db5f3ebb00f 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -1,34 +1,22 @@
.event-title
%span.author_name= link_to_author event
- %span.event_label commented on
- - if event.note_target
- - if event.note_commit?
- = event.note_target_type
- = link_to event.note_short_commit_id, project_commit_path(event.project, event.note_commit_id), class: "commit_short_id"
- - else
- = link_to [event.project, event.note_target] do
- %strong
- #{event.note_target_type} ##{truncate event.note_target_id}
-
- - elsif event.wall_note?
- = link_to 'wall', wall_project_path(event.project)
- - else
- %strong (deleted)
- at
+ %span.event_label commented on #{event_note_title_html(event)} at
- if event.project
= link_to_project event.project
- else
= event.project_name
.event-body
- %i.icon-comment-alt.event-note-icon
- %span.event-note
- = markdown truncate(event.target.note, length: 70)
+ .event-note
+ .md
+ %i.icon-comment-alt.event-note-icon
+ = event_note(event.target.note)
- note = event.target
- if note.attachment.url
- = link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do
- - if note.attachment.image?
+ - if note.attachment.image?
+ = link_to note.attachment.url, target: '_blank' do
= image_tag note.attachment.url, class: 'note-image-attach'
- - else
+ - else
+ = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do
%i.icon-paper-clip
= note.attachment_identifier
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 119b8e828d0..adba9a5f619 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -5,7 +5,7 @@
%strong= event.ref_name
- else
= link_to project_commits_path(event.project, event.ref_name) do
- %strong= event.ref_name
+ %strong= truncate(event.ref_name, length: 30)
at
%strong= link_to_project event.project
@@ -21,5 +21,5 @@
%li.commits-stat
- if event.commits_count > 2
%span ... and #{event.commits_count - 2} more commits.
- = link_to project_compare_path(event.project, from: event.parent_commit.id, to: event.last_commit.id) do
- %strong Compare &rarr; #{event.parent_commit.id[0..7]}...#{event.last_commit.id[0..7]}
+ = link_to project_compare_path(event.project, from: event.commit_from, to: event.commit_to) do
+ %strong Compare &rarr; #{event.commit_from[0..7]}...#{event.commit_to[0..7]}
diff --git a/app/views/graph/_head.html.haml b/app/views/graph/_head.html.haml
deleted file mode 100644
index fba9a958a19..00000000000
--- a/app/views/graph/_head.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-%h3.page_title Project Network Graph
-%hr
-
-.clearfix
- .pull-left
- = render partial: 'shared/ref_switcher', locals: {destination: 'graph', path: @path}
-
- .search.pull-right
- = form_tag project_graph_path(@project, params[:id]), method: :get do |f|
- .control-group
- = label_tag :search , "Looking for commit:", class: 'control-label light'
- .controls
- = text_field_tag :q, @q, placeholder: "Input SHA", class: "search-input xlarge"
- = button_tag type: 'submit', class: 'btn vtop' do
- %i.icon-search
-
diff --git a/app/views/graph/show.html.haml b/app/views/graph/show.html.haml
deleted file mode 100644
index e45aca1ddcb..00000000000
--- a/app/views/graph/show.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-= render "head"
-.graph_holder
- %h4
- %small You can move around the graph by using the arrow keys.
- #holder.graph
- .loading.loading-gray
-
-:javascript
- var branch_graph;
- $(function(){
- branch_graph = new BranchGraph($("#holder"), {
- url: '#{project_graph_path(@project, @ref, q: @q, format: :json)}',
- commit_url: '#{project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")}',
- ref: '#{@ref}',
- commit_id: '#{@commit.id}'
- });
- });
diff --git a/app/views/groups/_filter.html.haml b/app/views/groups/_filter.html.haml
index c14fc8e5e4b..9fbc6c190cc 100644
--- a/app/views/groups/_filter.html.haml
+++ b/app/views/groups/_filter.html.haml
@@ -1,11 +1,5 @@
= form_tag group_filter_path(entity), method: 'get' do
- %fieldset.dashboard-search-filter
- = search_field_tag "search", params[:search], { placeholder: 'Search', class: 'search-text-input' }
- = button_tag type: 'submit', class: 'btn' do
- %i.icon-search
-
%fieldset
- %legend Status:
%ul.nav.nav-pills.nav-stacked
%li{class: ("active" if !params[:status])}
= link_to group_filter_path(entity, status: nil) do
@@ -26,6 +20,8 @@
= link_to group_filter_path(entity, project_id: project.id) do
= 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
%fieldset
%hr
diff --git a/app/views/groups/_new_group_member.html.haml b/app/views/groups/_new_group_member.html.haml
index 9cdbea60370..234392c03e1 100644
--- a/app/views/groups/_new_group_member.html.haml
+++ b/app/views/groups/_new_group_member.html.haml
@@ -1,18 +1,20 @@
-= form_for @team_member, as: :team_member, url: team_members_group_path(@group) do |f|
+= form_for @users_group, url: group_users_groups_path(@group) do |f|
%fieldset
- %legend= "New Team member(s) for projects in #{@group.name}"
+ %legend
+ New member(s) for
+ %strong #{@group.name}
+ group
- %h6 1. Choose people you want in the team
- .clearfix
+ %p 1. Choose users you want in the group
+ .control-group
= f.label :user_ids, "People"
- .input= select_tag(:user_ids, options_from_collection_for_select(User.active.alphabetically, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true})
+ .controls= users_select_tag(:user_ids, multiple: true, class: 'input-large')
- %h6 2. Set access level for them
- .clearfix
- = f.label :project_access, "Project Access"
- .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen"
+ %p 2. Set access level for them
+ .control-group
+ = f.label :group_access, "Group Access"
+ .controls= select_tag :group_access, options_for_select(UsersGroup.group_access_roles, @users_group.group_access), class: "project-access-select chosen"
.form-actions
- = hidden_field_tag :redirect_to, people_group_path(@group)
- = f.submit 'Add', class: "btn btn-save"
+ = f.submit 'Add users into group', class: "btn btn-create"
diff --git a/app/views/groups/_new_member.html.haml b/app/views/groups/_new_member.html.haml
deleted file mode 100644
index b3424b01bcb..00000000000
--- a/app/views/groups/_new_member.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-= form_for @team_member, as: :team_member, url: project_team_members_path(@project, @team_member) do |f|
- %fieldset
- %legend= "New Team member(s) for #{@project.name}"
-
- %h6 1. Choose people you want in the team
- .clearfix
- = f.label :user_ids, "People"
- .input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).alphabetically, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true})
-
- %h6 2. Set access level for them
- .clearfix
- = f.label :project_access, "Project Access"
- .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen"
-
- .form-actions
- = hidden_field_tag :redirect_to, people_group_path(@group, project_id: @project.id)
- = f.submit 'Add', class: "btn btn-save"
-
diff --git a/app/views/groups/_people_filter.html.haml b/app/views/groups/_people_filter.html.haml
deleted file mode 100644
index 901a037adf3..00000000000
--- a/app/views/groups/_people_filter.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-= form_tag people_group_path(@group), method: 'get' do
- %fieldset
- %legend Projects:
- %ul.nav.nav-pills.nav-stacked
- - @projects.each do |project|
- %li{class: ("active" if params[:project_id] == project.id.to_s)}
- = link_to people_group_path(@group, project_id: project.id) do
- = project.name_with_namespace
- %small.pull-right= project.users.count
-
- %fieldset
- %hr
- = link_to "Reset", people_group_path(@group), class: 'btn pull-right'
-
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 4fa4a177983..16a3c60f660 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -1,22 +1,21 @@
-.projects_box
- %h5.title
- Projects
- %small
- (#{projects.count})
+.ui-box
+ .title
+ Projects (#{projects.count})
- if can? current_user, :manage_group, @group
%span.pull-right
- = link_to new_project_path(namespace_id: @group.id), class: "btn btn-tiny info" do
+ = link_to new_project_path(namespace_id: @group.id), class: "btn btn-new" do
%i.icon-plus
- New Project
+ New project
%ul.well-list
- if projects.blank?
%p.nothing_here_message This groups has no projects yet
- projects.each do |project|
- %li
+ %li.project-row
= link_to project_path(project), class: dom_class(project) do
- %strong.well-title= truncate(project.name, length: 25)
+ %span.project-name
+ = truncate(project.name, length: 25)
%span.arrow
- &rarr;
- %span.last_activity
- %strong Last activity:
- %span= project_last_activity(project)
+ %i.icon-angle-right
+ %span.last-activity
+ %span Last activity:
+ %span.date= project_last_activity(project)
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 7202ef26c70..2682d31ff47 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,50 +1,75 @@
-%h3.page_title Edit Group
-%hr
-= form_for @group do |f|
- - if @group.errors.any?
- .alert.alert-error
- %span= @group.errors.full_messages.first
- .clearfix
- = f.label :name do
- Group name is
- .input
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
- &nbsp;
- = f.submit 'Save group', class: "btn btn-save"
-%hr
+.row
+ .span2
+ %ul.nav.nav-pills.nav-stacked.nav-stacked-menu
+ %li.active
+ = link_to '#tab-edit', 'data-toggle' => 'tab' do
+ %i.icon-edit
+ Edit Group
+ %li
+ = link_to '#tab-projects', 'data-toggle' => 'tab' do
+ %i.icon-folder-close
+ Projects
+ %li
+ = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab'
+ .span10
+ .tab-content
+ .tab-pane.active#tab-edit
+ .ui-box
+ .title
+ %strong= @group.name
+ group settings:
+ %div.form-holder
+ = form_for @group do |f|
+ - if @group.errors.any?
+ .alert.alert-error
+ %span= @group.errors.full_messages.first
+ .control-group
+ = f.label :name do
+ Group name is
+ .controls
+ = f.text_field :name, placeholder: "Ex. OpenSource", class: "input-xxlarge left"
-.row
- .span7
- .ui-box
- %h5.title Projects
- %ul.well-list
- - @group.projects.each do |project|
- %li
- - if project.public
- %i.icon-share
- - else
- %i.icon-lock.cgreen
- = link_to project.name_with_namespace, project
- .pull-right
- = link_to 'Team', 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, confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
+ .control-group.group-description-holder
+ = f.label :description, "Details"
+ .controls
+ = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4
+
+ .form-actions
+ = f.submit 'Save group', class: "btn btn-save"
+
+ .tab-pane#tab-projects
+ .ui-box
+ .title
+ %strong= @group.name
+ projects:
+ - if can? current_user, :manage_group, @group
+ %span.pull-right
+ = link_to new_project_path(namespace_id: @group.id), class: "btn btn-tiny" do
+ %i.icon-plus
+ New Project
+ %ul.well-list
+ - @group.projects.each do |project|
+ %li
+ - if project.public
+ = public_icon
+ - else
+ = private_icon
+ = 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, 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
+
+ .tab-pane#tab-remove
+ .ui-box.ui-box-danger
+ .title Remove group
+ .ui-box-body
+ %p
+ Remove of group will cause removing all child projects and resources.
+ %p
+ %strong Removed group can not be restored!
- .span5
- .ui-box
- %h5.title Transfer group
- .padded
- %p
- Transferring group will cause loss of admin control over group and all child projects
- = form_for @group do |f|
- = f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
- = f.submit 'Transfer group', class: "btn btn-small"
- .ui-box
- %h5.title Remove group
- .padded.bgred
- %p
- Remove of group will cause removing all child projects and resources
- %br
- Removed group can not be restored!
- = link_to 'Remove Group', @group, confirm: 'Removed group can not be restored! Are you sure?', method: :delete, class: "btn btn-remove btn-small"
+ = link_to 'Remove Group', @group, confirm: 'Removed group can not be restored! Are you sure?', method: :delete, class: "btn btn-remove"
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 94682bdd51e..482613f172d 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,23 +1,15 @@
-%h3.page_title
- Issues
- %small (assigned to you)
- %small.pull-right #{@issues.total_count} issues
+%h3.page-title
+ Issues assigned to me
+ %span.pull-right #{@issues.total_count} issues
+%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.
%hr
+
.row
.span3
- = render 'filter', entity: 'issue'
+ = render 'shared/filter', entity: 'issue'
.span9
- - if @issues.any?
- - @issues.group_by(&:project).each do |group|
- %div.ui-box
- - project = group[0]
- %h5.title
- = link_to_project project
- %ul.well-list.issues_table
- - group[1].each do |issue|
- = render(partial: 'issues/show', locals: {issue: issue})
- %hr
- = paginate @issues, theme: "gitlab"
- - else
- %p.nothing_here_message Nothing to show here
+ = render 'shared/issues'
diff --git a/app/views/groups/members.html.haml b/app/views/groups/members.html.haml
new file mode 100644
index 00000000000..124560e4786
--- /dev/null
+++ b/app/views/groups/members.html.haml
@@ -0,0 +1,20 @@
+%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"
+
+%hr
+- can_manage_group = current_user.can? :manage_group, @group
+.ui-box
+ .title
+ %strong #{@group.name}
+ group members
+ %small
+ (#{@members.count})
+ %ul.well-list
+ - @members.each do |member|
+ = render 'users_groups/users_group', member: member, show_controls: can_manage_group
+- if can_manage_group
+ = render "new_group_member"
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index a311729dd4d..8a9b03535bc 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -1,11 +1,14 @@
-%h3.page_title
+%h3.page-title
Merge Requests
- %small (authored by or assigned to you)
- %small.pull-right #{@merge_requests.total_count} merge requests
+ %span.pull-right #{@merge_requests.total_count} merge requests
+%p.light
+ Authored or assigned to you from
+ %strong #{@group.name}
+ group. To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
%hr
.row
.span3
- = render 'filter', entity: 'merge_request'
+ = render 'shared/filter', entity: 'merge_request'
.span9
= render 'shared/merge_requests'
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 73be474e278..02049bb2ee6 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -1,21 +1,28 @@
-%h3.page_title New Group
-%hr
= form_for @group do |f|
- if @group.errors.any?
.alert.alert-error
%span= @group.errors.full_messages.first
- .clearfix
+ .control-group
= f.label :name do
Group name is
- .input
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
- &nbsp;
- = f.submit 'Create group', class: "btn btn-create"
- %hr
- .padded
- %ul
- %li Group is kind of directory for several projects
- %li All created groups are private
- %li People within a group see only projects they have access to
- %li All projects of group will be stored in group directory
- %li You will be able to move existing projects into group
+ .controls
+ = f.text_field :name, placeholder: "Ex. OpenSource", class: "input-xxlarge left"
+
+ .control-group.group-description-holder
+ = f.label :description, "Details"
+ .controls
+ = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4
+
+ .control-group
+ .controls
+ %ul
+ %li Group is kind of directory for several projects
+ %li All created groups are private
+ %li People within a group see only projects they have access to
+ %li All projects of group will be stored in a group directory
+ %li You will be able to move existing projects into group
+
+ .form-actions
+ = f.submit 'Create group', class: "btn btn-create"
+
+
diff --git a/app/views/groups/people.html.haml b/app/views/groups/people.html.haml
deleted file mode 100644
index 3e4eb082f56..00000000000
--- a/app/views/groups/people.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-.row
- .span3
- = render 'people_filter'
- .span9
- - if can?(current_user, :manage_group, @group)
- = render (@project ? "new_member" : "new_group_member")
- .ui-box
- %h5.title
- Team
- %small
- (#{@users.size})
- %ul.well-list
- - @users.each do |user|
- %li
- = image_tag gravatar_icon(user.email, 16), class: "avatar s16"
- %strong= user.name
- %span.cgray= user.email
- - if @group.owner == user
- %span.btn.btn-small.disabled.pull-right Group Owner
-
diff --git a/app/views/groups/search.html.haml b/app/views/groups/search.html.haml
deleted file mode 100644
index f56bbadeac0..00000000000
--- a/app/views/groups/search.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-= form_tag search_group_path(@group), method: :get, class: 'form-inline' do |f|
- .padded
- = label_tag :search do
- %strong Looking for
- .input
- = search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search"
- = submit_tag 'Search', class: "btn btn-primary wide"
-- if params[:search].present?
- = render 'search/result'
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index 9aa52ea5593..edf03642d82 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -1,17 +1,16 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
- xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}"
- xml.link :href => projects_url(:atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => projects_url, :rel => "alternate", :type => "text/html"
+ xml.title "Group feed - #{@group.name}"
+ xml.link :href => group_path(@group, :atom), :rel => "self", :type => "application/atom+xml"
+ xml.link :href => group_path(@group), :rel => "alternate", :type => "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
if event.proper?
- event = EventDecorator.decorate(event)
xml.entry do
- event_link = event.feed_url
- event_title = event.feed_title
+ event_link = event_feed_url(event)
+ event_title = event_feed_title(event)
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link :href => event_link
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index a140b401b9d..e613ed3eaa3 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,4 +1,4 @@
-.projects
+.dashboard
.activities.span8
= render "events/event_last_push", event: @last_push
= link_to dashboard_path, class: 'btn btn-tiny' do
@@ -6,24 +6,22 @@
&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
.loading.hide
.side.span4
+ - if @group.description.present?
+ .description-block
+ = @group.description
= render "projects", projects: @projects
- %div
- %span.rss-icon
- = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do
- = image_tag "rss_ui.png", title: "feed"
- %strong News Feed
+ .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
- .gitlab-promo
- = link_to "Homepage", "http://gitlabhq.com"
- = link_to "Blog", "http://blog.gitlabhq.com"
- = link_to "@gitlabhq", "https://twitter.com/gitlabhq"
-
-:javascript
- $(function(){ Pager.init(20, true); });
+ = render 'shared/promo'
diff --git a/app/views/help/_api_layout.html.haml b/app/views/help/_api_layout.html.haml
new file mode 100644
index 00000000000..502cc31a80c
--- /dev/null
+++ b/app/views/help/_api_layout.html.haml
@@ -0,0 +1,13 @@
+.row
+ .span3
+ .append-bottom-20
+ = link_to help_path, class: 'btn btn-small' do
+ %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|
+ %li{class: file == @category ? 'active' : nil}
+ = link_to file.titleize, help_api_file_path(file)
+
+ .span9.pull-right
+ = yield
diff --git a/app/views/help/_layout.html.haml b/app/views/help/_layout.html.haml
index fa5e3a30b29..ac8660dcbb4 100644
--- a/app/views/help/_layout.html.haml
+++ b/app/views/help/_layout.html.haml
@@ -2,7 +2,7 @@
.span3{:"data-spy" => 'affix'}
.ui-box
.title
- %h5 Help
+ Help
%ul.well-list
%li
%strong= link_to "Workflow", help_workflow_path
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
new file mode 100644
index 00000000000..e979e7c0d07
--- /dev/null
+++ b/app/views/help/_shortcuts.html.haml
@@ -0,0 +1,30 @@
+#modal-shortcuts.modal.hide
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3 Keyboard Shortcuts
+ .modal-body
+ %h5 Global Shortcuts
+ %p
+ %span.label.label-inverse s
+ &ndash;
+ Focus Search
+ %p
+ %span.label.label-inverse ?
+ &ndash;
+ Show this dialog
+
+ %h5 Project Files browsing
+ %p
+ %span.label.label-inverse
+ %i.icon-arrow-up
+ &ndash;
+ Move selection up
+ %p
+ %span.label.label-inverse
+ %i.icon-arrow-down
+ &ndash;
+ Move selection down
+ %p
+ %span.label.label-inverse Enter
+ &ndash;
+ Open selection
diff --git a/app/views/help/api.html.haml b/app/views/help/api.html.haml
index d771f1e9f38..3d2cf50b7f2 100644
--- a/app/views/help/api.html.haml
+++ b/app/views/help/api.html.haml
@@ -1,105 +1,14 @@
-= render layout: 'help/layout' do
- %h3.page_title API
- %br
-
- %ul.nav.nav-tabs.log-tabs.nav-small-tabs
- %li.active
- = link_to "README", "#README", 'data-toggle' => 'tab'
- %li
- = link_to "Projects", "#projects", 'data-toggle' => 'tab'
- %li
- = link_to "Snippets", "#snippets", 'data-toggle' => 'tab'
- %li
- = link_to "Repositories", "#repositories", 'data-toggle' => 'tab'
- %li
- = link_to "Users", "#users", 'data-toggle' => 'tab'
- %li
- = link_to "Session", "#session", 'data-toggle' => 'tab'
- %li
- = link_to "Issues", "#issues", 'data-toggle' => 'tab'
- %li
- = link_to "Milestones", "#milestones", 'data-toggle' => 'tab'
- %li
- = link_to "Notes", "#notes", 'data-toggle' => 'tab'
-
- .tab-content
- .tab-pane.active#README
- .file_holder
- .file_title
- %i.icon-file
- README
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "README.md"))
-
- .tab-pane#projects
- .file_holder
- .file_title
- %i.icon-file
- Projects
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "projects.md"))
-
- .tab-pane#snippets
- .file_holder
- .file_title
- %i.icon-file
- Projects Snippets
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "snippets.md"))
-
- .tab-pane#repositories
- .file_holder
- .file_title
- %i.icon-file
- Projects
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "repositories.md"))
-
- .tab-pane#users
- .file_holder
- .file_title
- %i.icon-file
- Users
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "users.md"))
-
- .tab-pane#session
- .file_holder
- .file_title
- %i.icon-file
- Session
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "session.md"))
-
- .tab-pane#issues
- .file_holder
- .file_title
- %i.icon-file
- Issues
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "issues.md"))
-
- .tab-pane#milestones
- .file_holder
- .file_title
- %i.icon-file
- Milestones
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "milestones.md"))
-
- .tab-pane#notes
- .file_holder
- .file_title
- %i.icon-file
- Notes
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "notes.md"))
+= render layout: 'help/api_layout' do
+ %h3.page-title
+ %span.light API
+ %span
+ \/
+ = @category.titleize
+
+ .file-holder
+ .file-title
+ %i.icon-file
+ = @category
+ .file-content.wiki
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "api", "#{@category}.md"))
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index 1a4411c8f30..ff01136f5bb 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -1,39 +1,42 @@
-%h3.page_title
- GITLAB
+%h2.page-title
+ GitLab
.pull-right
- %span= Gitlab::Version
- %small= Gitlab::Revision
-%hr
-%p.lead
+ %span= Gitlab::VERSION
+ %small= Gitlab::REVISION
+%p.slead
Self Hosted Git Management
%br
- Fast, secure and stable solution based on Ruby on Rails & Gitolite.
-
-%br
+ Fast, secure and stable solution based on Ruby on Rails.
.row
.span4
.ui-box
.title
- %h5 Quick help
+ Quick help
%ul.well-list
%li
Email your
- = mail_to Gitlab.config.gitlab.support_email, "support contact"
+ = mail_to gitlab_config.support_email, "support contact"
%li
Use the
- = link_to "search bar", '#', onclick: "$("#search").focus();"
+ = link_to "search bar", '#', onclick: "$('#search').focus();"
on the top of this page
%li
+ Use
+ = link_to "shortcuts", '#', onclick: "new Shortcuts()"
+ %li
Ask in our
- = link_to "support forum", "https://groups.google.com/forum/#!forum/gitlabhq"
+ = link_to "mailing list", "https://groups.google.com/forum/#!forum/gitlabhq"
+ or on
+ = link_to "Stack Overflow", "http://stackoverflow.com/questions/tagged/gitlab"
%li
Browse our
= link_to "issue tracker", "https://github.com/gitlabhq/gitlabhq/issues"
+
.span4
.ui-box
.title
- %h5 User documentation
+ User documentation
%ul.well-list
%li
%strong= link_to "Workflow", help_workflow_path
@@ -62,7 +65,7 @@
.span4
.ui-box
.title
- %h5 Admin documentation
+ Admin documentation
%ul.well-list
%li
diff --git a/app/views/help/markdown.html.haml b/app/views/help/markdown.html.haml
index 92c1e49be49..ec9d13f2d6b 100644
--- a/app/views/help/markdown.html.haml
+++ b/app/views/help/markdown.html.haml
@@ -1,127 +1,6 @@
= render layout: 'help/layout' do
- %h3.page_title GitLab Flavored Markdown
- %br
+ %h3.page-title GitLab Flavored Markdown
- .row
- .span8
- %p
- For GitLab we developed something we call "GitLab Flavored Markdown" (GFM).
- It extends the standard Markdown in a few significant ways adds some useful functionality.
-
- %p You can use GFM in:
- %ul
- %li commit messages
- %li comments
- %li wall posts
- %li issues
- %li merge requests
- %li milestones
- %li wiki pages
-
- .span4
- .alert.alert-info
- %p
- If you're not already familiar with Markdown, you should spend 15 minutes and go over the excellent
- %strong= link_to "Markdown Syntax Guide", "http://daringfireball.net/projects/markdown/syntax"
- at Daring Fireball.
-
- .row
- .span8
- %h3 Differences from traditional Markdown
-
- %h4 Newlines
-
- %p
- 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.
-
-
- %p The next paragraph contains two phrases separated by a single newline character:
- %pre= "Roses are red\nViolets are blue"
- %p becomes
- = markdown "Roses are red\nViolets are blue"
-
- %h4 Multiple underscores in words
-
- %p
- It is not reasonable to italicize just <em>part</em> of a word, especially when you're dealing with code and names often appear with multiple underscores.
- Therefore, GFM ignores multiple underscores in words.
-
- %pre= "perform_complicated_task\ndo_this_and_do_that_and_another_thing"
- %p becomes
- = markdown "perform_complicated_task\ndo_this_and_do_that_and_another_thing"
-
- %h4 URL autolinking
-
- %p
- GFM will autolink standard URLs you copy and paste into your text.
- So if you want to link to a URL (instead of a textual link), you can simply put the URL in verbatim and it will be turned into a link to that URL.
-
- %h4 Fenced code blocks
-
- %p
- Markdown converts text with four spaces at the front of each line to code blocks.
- GFM supports that, but we also support fenced blocks.
- Just wrap your code blocks in <code>```</code> and you won't need to indent manually to trigger a code block.
-
- %pre= %Q{```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new("Hello World!")\nputs markdown.to_html\n```}
- %p becomes
- = markdown %Q{```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new("Hello World!")\nputs markdown.to_html\n```}
-
- %h4 Emoji
-
- .row
- .span8
- :ruby
- puts markdown %Q{Sometimes you want to be :cool: and add some :sparkles: to your :speech_balloon:. Well we have a :gift: for you:
-
- :exclamation: You can use emoji anywhere GFM is supported. :sunglasses:
-
- You can use it to point out a :bug: or warn about :monkey:patches. And if someone improves your really :snail: code, send them a :bouquet: or some :candy:. People will :heart: you for that.
-
- If you are :new: to this, don't be :fearful:. You can easily join the emoji :circus_tent:. All you need to do is to :book: up on the supported codes.
- }
-
- .span4
- .alert.alert-info
- %p
- Consult the
- %strong= link_to "Emoji Cheat Sheet", "http://www.emoji-cheat-sheet.com/"
- for a list of all supported emoji codes.
-
- .row
- .span8
- %h4 Special GitLab references
-
- %p
- GFM recognizes special references.
- You can easily reference e.g. a team member, an issue or a commit within a project.
- GFM will turn that reference into a link so you can navigate between them easily.
-
- %p GFM will recognize the following references:
- %ul
- %li
- %code @foo
- for team members
- %li
- %code #123
- for issues
- %li
- %code !123
- for merge request
- %li
- %code $123
- for snippets
- %li
- %code 1234567
- for commits
-
- -# this example will only be shown if the user has a project with at least one issue
- - if @project = current_user.authorized_projects.first
- - if issue = @project.issues.first
- %p For example in your #{link_to @project.name, project_path(@project)} project, writing:
- %pre= "This is related to ##{issue.id}. @#{current_user.username} is working on solving it."
- %p becomes:
- = markdown "This is related to ##{issue.id}. @#{current_user.username} is working on solving it."
- - @project = nil # Prevent this from bubbling up to page title
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "markdown", "markdown.md"))
diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml
index 2075753e82a..bab1e7c0a41 100644
--- a/app/views/help/permissions.html.haml
+++ b/app/views/help/permissions.html.haml
@@ -1,66 +1,210 @@
= render layout: 'help/layout' do
- %h3.page_title Permissions
- %br
+ %h3.page-title Permissions
+ %p.light User has different abilities depends on access level he has in particular group or project
+ %hr
- %fieldset
- %legend Guest
- %ul
- %li Create new issue
- %li Leave comments
- %li Write on project wall
+ %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 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 public mode
+ %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;
- %fieldset
- %legend Reporter
- %ul
- %li Create new issue
- %li Leave comments
- %li Write on project wall
- %li Pull project code
- %li Download project
- %li Create a code snippets
-
-
- %fieldset
- %legend Developer
- %ul
- %li Create new issue
- %li Leave comments
- %li Write on project wall
- %li Pull project code
- %li Download project
- %li Create new merge request
- %li Create a code snippets
- %li Create new branches
- %li Push to non-protected branches
- %li Remove non-protected branches
- %li Add tags
- %li Write a wiki
-
- %fieldset
- %legend Master
- %ul
- %li Create new issue
- %li Leave comments
- %li Write on project wall
- %li Pull project code
- %li Download project
- %li Create new merge request
- %li Create a code snippets
- %li Create new branches
- %li Push to non-protected branches
- %li Remove non-protected branches
- %li Add tags
- %li Write a wiki
- %li Add new team members
- %li Push to protected branches
- %li Remove protected branches
- %li Push with force option
- %li Edit project
- %li Add Deploy Keys to project
- %li Configure Project Hooks
-
- %fieldset
- %legend Owner
- %ul
- %li Transfer project to another namespace
- %li Remove project
+ %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;
diff --git a/app/views/help/public_access.html.haml b/app/views/help/public_access.html.haml
index 66de17a34ed..c67402ee319 100644
--- a/app/views/help/public_access.html.haml
+++ b/app/views/help/public_access.html.haml
@@ -1,10 +1,9 @@
= render layout: 'help/layout' do
- %h3.page_title Public Access
- %br
+ %h3.page-title Public Access
%p
GitLab allows you to open selected projects to be accessed publicly.
- These projects will be clonable
+ These projects will be cloneable
%em without any
authentication.
Also they will be listed on the #{link_to "public access directory", public_root_path}.
diff --git a/app/views/help/raketasks.html.haml b/app/views/help/raketasks.html.haml
index bcc874fc390..17a02e913ee 100644
--- a/app/views/help/raketasks.html.haml
+++ b/app/views/help/raketasks.html.haml
@@ -1,6 +1,5 @@
= render layout: 'help/layout' do
- %h3.page_title GitLab Rake Tasks
- %br
+ %h3.page-title GitLab Rake Tasks
%p.slead
GitLab provides some specific rake tasks to enable special features or perform maintenance tasks.
@@ -19,46 +18,46 @@
.tab-content
.tab-pane.active#features
- .file_holder
- .file_title
+ .file-holder
+ .file-title
%i.icon-file
Features
- .file_content.wiki
+ .file-content.wiki
= preserve do
= markdown File.read(Rails.root.join("doc", "raketasks", "features.md"))
.tab-pane#maintenance
- .file_holder
- .file_title
+ .file-holder
+ .file-title
%i.icon-file
Maintenance
- .file_content.wiki
+ .file-content.wiki
= preserve do
= markdown File.read(Rails.root.join("doc", "raketasks", "maintenance.md"))
.tab-pane#user_management
- .file_holder
- .file_title
+ .file-holder
+ .file-title
%i.icon-file
User Management
- .file_content.wiki
+ .file-content.wiki
= preserve do
= markdown File.read(Rails.root.join("doc", "raketasks", "user_management.md"))
.tab-pane#cleanup
- .file_holder
- .file_title
+ .file-holder
+ .file-title
%i.icon-file
Cleanup
- .file_content.wiki
+ .file-content.wiki
= preserve do
= markdown File.read(Rails.root.join("doc", "raketasks", "cleanup.md"))
.tab-pane#backup_restore
- .file_holder
- .file_title
+ .file-holder
+ .file-title
%i.icon-file
Backup & Restore
- .file_content.wiki
+ .file-content.wiki
= preserve do
= markdown File.read(Rails.root.join("doc", "raketasks", "backup_restore.md"))
diff --git a/app/views/help/shortcuts.js.haml b/app/views/help/shortcuts.js.haml
new file mode 100644
index 00000000000..99ed042ea3b
--- /dev/null
+++ b/app/views/help/shortcuts.js.haml
@@ -0,0 +1,3 @@
+:plain
+ $("body").append("#{escape_javascript(render('shortcuts'))}");
+ $("#modal-shortcuts").modal();
diff --git a/app/views/help/ssh.html.haml b/app/views/help/ssh.html.haml
index 114415977b4..72a21b89cab 100644
--- a/app/views/help/ssh.html.haml
+++ b/app/views/help/ssh.html.haml
@@ -1,6 +1,5 @@
= render layout: 'help/layout' do
- %h3.page_title SSH Keys
- %br
+ %h3.page-title SSH Keys
%p.slead
SSH key allows you to establish a secure connection between your computer and GitLab
diff --git a/app/views/help/system_hooks.html.haml b/app/views/help/system_hooks.html.haml
index c49011a2269..3e401a6e19f 100644
--- a/app/views/help/system_hooks.html.haml
+++ b/app/views/help/system_hooks.html.haml
@@ -1,6 +1,5 @@
= render layout: 'help/layout' do
- %h3.page_title System hooks
- %br
+ %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.
diff --git a/app/views/help/web_hooks.html.haml b/app/views/help/web_hooks.html.haml
index 09745f73614..25865251de3 100644
--- a/app/views/help/web_hooks.html.haml
+++ b/app/views/help/web_hooks.html.haml
@@ -1,6 +1,5 @@
= render layout: 'help/layout' do
- %h3.page_title Web hooks
- %br
+ %h3.page-title Web hooks
%p.slead
Every GitLab project can trigger a web server whenever the repo is pushed to.
@@ -9,5 +8,5 @@
%br
GitLab will send POST request with commits information on every push.
%h5 Hooks request example:
- = render "hooks/data_ex"
+ = render "projects/hooks/data_ex"
diff --git a/app/views/help/workflow.html.haml b/app/views/help/workflow.html.haml
index 495b7c6e6fc..2b8950cd5c2 100644
--- a/app/views/help/workflow.html.haml
+++ b/app/views/help/workflow.html.haml
@@ -1,6 +1,5 @@
= render layout: 'help/layout' do
- %h3.page_title Workflow
- %br
+ %h3.page-title Workflow
%ol.help
%li
diff --git a/app/views/issues/_filter.html.haml b/app/views/issues/_filter.html.haml
deleted file mode 100644
index 21efaa5357c..00000000000
--- a/app/views/issues/_filter.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-= form_tag project_issues_path(@project), method: 'get' do
- %fieldset
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if !params[:status])}
- = link_to project_issues_path(@project, status: nil) do
- Open
- %li{class: ("active" if params[:status] == 'assigned-to-me')}
- = link_to project_issues_path(@project, status: 'assigned-to-me') do
- Assigned To Me
- %li{class: ("active" if params[:status] == 'closed')}
- = link_to project_issues_path(@project, status: 'closed') do
- Closed
- %li{class: ("active" if params[:status] == 'all')}
- = link_to project_issues_path(@project, status: 'all') do
- All
-
- %fieldset
- %hr
- = link_to "Reset", project_issues_path(@project), class: 'btn pull-right'
-
diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml
deleted file mode 100644
index 6d7613a700d..00000000000
--- a/app/views/issues/_form.html.haml
+++ /dev/null
@@ -1,85 +0,0 @@
-%div.issue-form-holder
- %h3.page_title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.id}"
- = form_for [@project, @issue] do |f|
- -if @issue.errors.any?
- .alert.alert-error
- - @issue.errors.full_messages.each do |msg|
- %span= msg
- %br
- .ui-box.ui-box-show
- .ui-box-head
- .clearfix
- = f.label :title do
- %strong= "Subject *"
- .input
- = f.text_field :title, maxlength: 255, class: "xxlarge js-gfm-input", autofocus: true, required: true
- .ui-box-body
- .clearfix
- .issue_assignee.pull-left
- = f.label :assignee_id do
- %i.icon-user
- Assign to
- .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'})
- .issue_milestone.pull-left
- = f.label :milestone_id do
- %i.icon-time
- Milestone
- .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
-
- .ui-box-bottom
- .clearfix
- = f.label :label_list do
- %i.icon-tag
- Labels
- .input
- = f.text_field :label_list, maxlength: 2000, class: "xxlarge"
- %p.hint Separate with comma.
-
- .clearfix
- = f.label :description, "Details"
- .input
- = f.text_area :description, maxlength: 2000, class: "xxlarge js-gfm-input", rows: 14
- %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
-
-
- .actions
- - if @issue.new_record?
- = f.submit 'Submit new issue', class: "btn btn-create"
- -else
- = f.submit 'Save changes', class: "btn-save btn"
-
- - cancel_path = @issue.new_record? ? project_issues_path(@project) : project_issue_path(@project, @issue)
- = link_to "Cancel", cancel_path, class: 'btn btn-cancel'
-
-
-
-
-:javascript
- $(function(){
- $("#issue_label_list")
- .bind( "keydown", function( event ) {
- if ( event.keyCode === $.ui.keyCode.TAB &&
- $( this ).data( "autocomplete" ).menu.active ) {
- event.preventDefault();
- }
- })
- .autocomplete({
- minLength: 0,
- source: function( request, response ) {
- response( $.ui.autocomplete.filter(
- #{raw labels_autocomplete_source}, extractLast( request.term ) ) );
- },
- focus: function() {
- return false;
- },
- select: function(event, ui) {
- var terms = split( this.value );
- terms.pop();
- terms.push( ui.item.value );
- terms.push( "" );
- this.value = terms.join( ", " );
- return false;
- }
- });
- });
-
diff --git a/app/views/issues/_issues.html.haml b/app/views/issues/_issues.html.haml
deleted file mode 100644
index 3bbd293dba2..00000000000
--- a/app/views/issues/_issues.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-- @issues.each do |issue|
- = render(partial: 'issues/show', locals: {issue: issue})
-
-- if @issues.present?
- %li.bottom
- .left= paginate @issues, remote: true, theme: "gitlab"
- .pull-right
- %span.issue_counter #{@issues.total_count}
- issues for this filter
-- else
- %li
- %h4.nothing_here_message Nothing to show here
-
diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml
deleted file mode 100644
index 875f29e2600..00000000000
--- a/app/views/issues/index.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-= render "issues/head"
-.issues_content
- %h3.page_title
- Issues
- %span (<span class=issue_counter>#{@issues.total_count}</span>)
- .pull-right
- .span5
- - 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-primary pull-right", title: "New Issue", id: "new_issue_link" do
- %i.icon-plus
- New Issue
- = form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: 'pull-right' do
- = hidden_field_tag :project_id, @project.id, { id: 'project_id' }
- = hidden_field_tag :status, params[:status]
- = search_field_tag :issue_search, nil, { placeholder: 'Search', class: 'issue_search span3 pull-right neib search-text-input' }
-
- .clearfix
-
-.row
- .span3
- = render 'filter', entity: 'issue'
- .span9
- %div#issues-table-holder.ui-box
- .title
- = 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_issues_text Update selected issues with &nbsp;
- .left
- = select_tag('update[status]', options_for_select(['open', 'closed']), prompt: "Status")
- = select_tag('update[assignee_id]', options_from_collection_for_select(@project.users.all, "id", "name", params[:assignee_id]), prompt: "Assignee")
- = select_tag('update[milestone_id]', options_from_collection_for_select(issues_active_milestones, "id", "title", params[:milestone_id]), 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
- = form_tag project_issues_path(@project), method: :get do
- = select_tag(:label_name, options_for_select(issue_tags, params[:label_name]), prompt: "Labels")
- = select_tag(:assignee_id, options_from_collection_for_select([unassigned_filter] + @project.users.all, "id", "name", params[:assignee_id]), prompt: "Assignee")
- = select_tag(:milestone_id, options_from_collection_for_select([unassigned_filter] + issues_active_milestones, "id", "title", params[:milestone_id]), prompt: "Milestone")
- = hidden_field_tag :status, params[:status]
-
- %ul#issues-table.well-list.issues_table
- = render "issues"
-
-:javascript
- $(function(){
- issuesPage();
- })
diff --git a/app/views/issues/index.js.haml b/app/views/issues/index.js.haml
deleted file mode 100644
index 48d7f582be2..00000000000
--- a/app/views/issues/index.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
- $('#issues-table').html("#{escape_javascript(render('issues'))}");
diff --git a/app/views/issues/show.html.haml b/app/views/issues/show.html.haml
deleted file mode 100644
index 474955cc665..00000000000
--- a/app/views/issues/show.html.haml
+++ /dev/null
@@ -1,59 +0,0 @@
-%h3.page_title
- Issue ##{@issue.id}
-
- %small
- created at
- = @issue.created_at.stamp("Aug 21, 2011")
-
- %span.pull-right
- - if can?(current_user, :admin_project, @project) || @issue.author == current_user
- - if @issue.closed
- = link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn grouped reopen_issue"
- - else
- = link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue"
- - if can?(current_user, :admin_project, @project) || @issue.author == current_user
- = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do
- %i.icon-edit
- Edit
-
-.pull-right
- .span3#votes= render 'votes/votes_block', votable: @issue
-
-.back_link
- = link_to project_issues_path(@project) do
- &larr; To issues list
-
-
-.ui-box.ui-box-show
- .ui-box-head
- %h4.box-title
- - if @issue.closed
- .error.status_info Closed
- = gfm escape_once(@issue.title)
-
- .ui-box-body
- %cite.cgray
- Created by #{link_to_member(@project, @issue.author)}
- - if @issue.assignee
- \ and currently assigned to #{link_to_member(@project, @issue.assignee)}
-
- - if @issue.milestone
- - milestone = @issue.milestone
- %cite.cgray and attached to milestone
- %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
-
- .pull-right
- - @issue.labels.each do |label|
- %span.label
- %i.icon-tag
- = label.name
- &nbsp;
-
- - if @issue.description.present?
- .ui-box-bottom
- .wiki
- = preserve do
- = markdown @issue.description
-
-
-.voting_notes#notes= render "notes/notes_with_form"
diff --git a/app/views/kaminari/admin/_first_page.html.haml b/app/views/kaminari/admin/_first_page.html.haml
deleted file mode 100644
index 41c9c0b3af6..00000000000
--- a/app/views/kaminari/admin/_first_page.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
--# Link to the "First" page
--# available local variables
--# url: url to the first page
--# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
--# per_page: number of items to fetch per page
--# remote: data-remote
-%span.first
- = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote
diff --git a/app/views/kaminari/admin/_gap.html.haml b/app/views/kaminari/admin/_gap.html.haml
deleted file mode 100644
index 3ffd12f8587..00000000000
--- a/app/views/kaminari/admin/_gap.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
--# Non-link tag that stands for skipped pages...
--# available local variables
--# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
--# per_page: number of items to fetch per page
--# remote: data-remote
-%li{class: "page"}
- %span.page.gap
- = raw(t 'views.pagination.truncate')
diff --git a/app/views/kaminari/admin/_last_page.html.haml b/app/views/kaminari/admin/_last_page.html.haml
deleted file mode 100644
index b03a206224c..00000000000
--- a/app/views/kaminari/admin/_last_page.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
--# Link to the "Last" page
--# available local variables
--# url: url to the last page
--# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
--# per_page: number of items to fetch per page
--# remote: data-remote
-%span.last
- = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {remote: remote}
diff --git a/app/views/kaminari/admin/_next_page.html.haml b/app/views/kaminari/admin/_next_page.html.haml
deleted file mode 100644
index 00c5f0b6f4e..00000000000
--- a/app/views/kaminari/admin/_next_page.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
--# Link to the "Next" page
--# available local variables
--# url: url to the next page
--# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
--# per_page: number of items to fetch per page
--# remote: data-remote
-%li.next
- = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote
diff --git a/app/views/kaminari/admin/_page.html.haml b/app/views/kaminari/admin/_page.html.haml
deleted file mode 100644
index a52d883b9a8..00000000000
--- a/app/views/kaminari/admin/_page.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
--# Link showing page number
--# available local variables
--# page: a page object for "this" page
--# url: url to this page
--# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
--# per_page: number of items to fetch per page
--# remote: data-remote
-%li{class: "page#{' active' if page.current?}"}
- = link_to page, url, {remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil}
diff --git a/app/views/kaminari/admin/_paginator.html.haml b/app/views/kaminari/admin/_paginator.html.haml
deleted file mode 100644
index 6f9fb332261..00000000000
--- a/app/views/kaminari/admin/_paginator.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
--# The container tag
--# available local variables
--# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
--# per_page: number of items to fetch per page
--# remote: data-remote
--# paginator: the paginator that renders the pagination tags inside
-= paginator.render do
- %div.pagination
- %ul
- = prev_page_tag unless current_page.first?
- - each_page do |page|
- - if page.left_outer? || page.right_outer? || page.inside_window?
- = page_tag page
- - elsif !page.was_truncated?
- = gap_tag
- = next_page_tag unless current_page.last?
diff --git a/app/views/kaminari/admin/_prev_page.html.haml b/app/views/kaminari/admin/_prev_page.html.haml
deleted file mode 100644
index f673abdb3ae..00000000000
--- a/app/views/kaminari/admin/_prev_page.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
--# Link to the "Previous" page
--# available local variables
--# url: url to the previous page
--# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
--# per_page: number of items to fetch per page
--# remote: data-remote
-%li{class: "prev" }
- = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote
diff --git a/app/views/kaminari/gitlab/_gap.html.haml b/app/views/kaminari/gitlab/_gap.html.haml
index f82f185ac35..3ffd12f8587 100644
--- a/app/views/kaminari/gitlab/_gap.html.haml
+++ b/app/views/kaminari/gitlab/_gap.html.haml
@@ -4,5 +4,6 @@
-# num_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-%span.page.gap
- = raw(t 'views.pagination.truncate')
+%li{class: "page"}
+ %span.page.gap
+ = raw(t 'views.pagination.truncate')
diff --git a/app/views/kaminari/gitlab/_next_page.html.haml b/app/views/kaminari/gitlab/_next_page.html.haml
index 296cceb080b..00c5f0b6f4e 100644
--- a/app/views/kaminari/gitlab/_next_page.html.haml
+++ b/app/views/kaminari/gitlab/_next_page.html.haml
@@ -5,5 +5,5 @@
-# num_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-%span.next
+%li.next
= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote
diff --git a/app/views/kaminari/gitlab/_page.html.haml b/app/views/kaminari/gitlab/_page.html.haml
index 19456dcc058..a52d883b9a8 100644
--- a/app/views/kaminari/gitlab/_page.html.haml
+++ b/app/views/kaminari/gitlab/_page.html.haml
@@ -6,5 +6,5 @@
-# num_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-%span{class: "page#{' current' if page.current?}"}
- = link_to_unless page.current?, page, url, {remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil}
+%li{class: "page#{' active' if page.current?}"}
+ = link_to page, url, {remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil}
diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml
index 6dd5a5782a2..6f9fb332261 100644
--- a/app/views/kaminari/gitlab/_paginator.html.haml
+++ b/app/views/kaminari/gitlab/_paginator.html.haml
@@ -6,11 +6,12 @@
-# remote: data-remote
-# paginator: the paginator that renders the pagination tags inside
= paginator.render do
- %nav.gitlab_pagination
- = prev_page_tag
- - each_page do |page|
- - if page.left_outer? || page.right_outer? || page.inside_window?
- = page_tag page
- - elsif !page.was_truncated?
- = gap_tag
- = next_page_tag
+ %div.pagination
+ %ul
+ = prev_page_tag unless current_page.first?
+ - each_page do |page|
+ - if page.left_outer? || page.right_outer? || page.inside_window?
+ = page_tag page
+ - elsif !page.was_truncated?
+ = gap_tag
+ = next_page_tag unless current_page.last?
diff --git a/app/views/kaminari/gitlab/_prev_page.html.haml b/app/views/kaminari/gitlab/_prev_page.html.haml
index 5c2061690ac..f673abdb3ae 100644
--- a/app/views/kaminari/gitlab/_prev_page.html.haml
+++ b/app/views/kaminari/gitlab/_prev_page.html.haml
@@ -5,5 +5,5 @@
-# num_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-%span.prev
+%li{class: "prev" }
= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote
diff --git a/app/views/keys/_form.html.haml b/app/views/keys/_form.html.haml
deleted file mode 100644
index fe26216b1d5..00000000000
--- a/app/views/keys/_form.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-%div
- = form_for @key do |f|
- -if @key.errors.any?
- .alert.alert-error
- %ul
- - @key.errors.full_messages.each do |msg|
- %li= msg
-
- .clearfix
- = f.label :title
- .input= f.text_field :title
- .clearfix
- = f.label :key
- .input
- = f.text_area :key, class: [:xxlarge, :thin_area]
- %p.hint
- Paste your public key here. Read more about how generate it
- = link_to "here", help_ssh_path
-
-
- .actions
- = f.submit 'Save', class: "btn btn-save"
- = link_to "Cancel", keys_path, class: "btn btn-cancel"
-
diff --git a/app/views/keys/_show.html.haml b/app/views/keys/_show.html.haml
deleted file mode 100644
index 52bbea6fc7b..00000000000
--- a/app/views/keys/_show.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-%tr
- %td
- = link_to key_path(key) do
- %strong= key.title
- %td
- %span.cgray
- Added
- = time_ago_in_words(key.created_at)
- ago
- %td
- = link_to 'Remove', key, confirm: 'Are you sure?', method: :delete, class: "btn btn-small btn-remove delete-key pull-right"
-
diff --git a/app/views/keys/edit.html.haml b/app/views/keys/edit.html.haml
deleted file mode 100644
index 60a3afedddc..00000000000
--- a/app/views/keys/edit.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-%h1 Editing key
-
-= render 'form'
-
-= link_to 'Show', @key
-\|
-= link_to 'Back', keys_path
diff --git a/app/views/keys/index.html.haml b/app/views/keys/index.html.haml
deleted file mode 100644
index 7730b344a7d..00000000000
--- a/app/views/keys/index.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-%h3.page_title
- SSH Keys
- = link_to "Add new", new_key_path, class: "btn pull-right"
-
-%hr
-%p.slead
- SSH key allows you to establish a secure connection between your computer and GitLab
-
-
-%table#keys-table
- %thead
- %tr
- %th Name
- %th Added
- %th
- - @keys.each do |key|
- = render(partial: 'show', locals: {key: key})
- - if @keys.blank?
- %tr
- %td{colspan: 3}
- %p.nothing_here_message There are no SSH keys with access to your account.
-
diff --git a/app/views/keys/show.html.haml b/app/views/keys/show.html.haml
deleted file mode 100644
index 059fe5e5806..00000000000
--- a/app/views/keys/show.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-%h3.page_title
- Public key:
- = @key.title
- %small
- created at
- = @key.created_at.stamp("Aug 21, 2011")
-.back_link
- = link_to keys_path do
- &larr; To keys list
-%hr
-
-%pre= @key.key
-.pull-right
- = link_to 'Remove', @key, confirm: 'Are you sure?', method: :delete, class: "btn btn-remove delete-key"
diff --git a/app/views/labels/_label.html.haml b/app/views/labels/_label.html.haml
deleted file mode 100644
index 027b041d58e..00000000000
--- a/app/views/labels/_label.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-%li
- %strong
- %i.icon-tag
- = label.name
- .pull-right
- = link_to project_issues_path(label_name: label.name) do
- %strong
- = pluralize(label.count, 'issue')
- = "»"
diff --git a/app/views/labels/index.html.haml b/app/views/labels/index.html.haml
deleted file mode 100644
index 6eb2c00e56d..00000000000
--- a/app/views/labels/index.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-= render "issues/head"
-
-%h3.page_title
- Labels
-%br
-%div.ui-box
- %ul.well-list.labels-table
- - @labels.each do |label|
- = render 'label', label: label
-
- - unless @labels.present?
- %li
- %h3.nothing_here_message Nothing to show here
-
diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml
index 9961ce8dd34..cc8ea066cb9 100644
--- a/app/views/layouts/_flash.html.haml
+++ b/app/views/layouts/_flash.html.haml
@@ -1,3 +1,8 @@
-- if text = alert || notice
- #flash-container
- %h4= text
+.flash-container
+ - if alert
+ .flash-alert
+ = alert
+
+ - elsif notice
+ .flash-notice
+ = notice
diff --git a/app/views/layouts/_google_analytics.html.haml b/app/views/layouts/_google_analytics.html.haml
new file mode 100644
index 00000000000..81e03c7eff2
--- /dev/null
+++ b/app/views/layouts/_google_analytics.html.haml
@@ -0,0 +1,10 @@
+:javascript
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', '#{extra_config.google_analytics_id}']);
+ _gaq.push(['_trackPageview']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 4b4f5da3324..0775abea3dd 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -7,6 +7,9 @@
= stylesheet_link_tag "application"
= javascript_include_tag "application"
= csrf_meta_tags
+ = include_gon
+
+ = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_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 8f4f3d7815f..6492c122ba0 100644
--- a/app/views/layouts/_head_panel.html.haml
+++ b/app/views/layouts/_head_panel.html.haml
@@ -8,31 +8,33 @@
%span.separator
%h1.project_name= title
%ul.nav
+ %li
+ %a
+ %div.hide.turbolink-spinner
+ %i.icon-refresh.icon-spin
+ Loading...
+ %li
+ = render "layouts/search"
+ %li
+ = link_to public_root_path, title: "Public area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
+ %i.icon-globe
+ %li
+ = link_to user_snippets_path(current_user), title: "My snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
+ %i.icon-paste
- if current_user.is_admin?
%li
= link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do
%i.icon-cogs
- if current_user.can_create_project?
%li
- = link_to new_project_path, title: "Create New Project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do
+ = link_to new_project_path, title: "New project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do
%i.icon-plus
%li
- = link_to profile_path, title: "My Profile", class: 'has_bottom_tooltip', 'data-original-title' => 'Your profile' do
+ = link_to profile_path, title: "My profile", class: 'has_bottom_tooltip', 'data-original-title' => 'My profile' do
%i.icon-user
- %li.separator
%li
- = render "layouts/search"
+ = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do
+ %i.icon-signout
%li
- .account-box
- = link_to profile_path, class: "pic" do
- = image_tag gravatar_icon(current_user.email)
- .account-links
- = link_to profile_path, class: "username" do
- %i.icon-user.icon-white
- My profile
- = link_to destroy_user_session_path, class: "logout", method: :delete do
- %i.icon-signout.icon-white
- Logout
-
-
-= render "layouts/init_auto_complete"
+ = link_to current_user, class: "profile-pic", id: 'profile-pic' do
+ = image_tag gravatar_icon(current_user.email, 26), alt: ''
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 8f8c7d8885e..7d16a75af6e 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -1,17 +1,4 @@
:javascript
- $(function() {
- GitLab.GfmAutoComplete.Members.url = "#{ "/api/v3/projects/#{@project.id}/members" if @project }";
- GitLab.GfmAutoComplete.Members.params.private_token = "#{current_user.private_token}";
-
- GitLab.GfmAutoComplete.Emoji.data = #{raw emoji_autocomplete_source};
- // convert the list so that the items have the right format for completion
- GitLab.GfmAutoComplete.Emoji.data = $.map(GitLab.GfmAutoComplete.Emoji.data, function(value) {
- return {
- name: value,
- insert: value+':',
- image: '#{image_path("emoji")}/'+value+'.png'
- }
- });
-
- GitLab.GfmAutoComplete.setup();
- });
+ GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_project_path(@project)}"
+ GitLab.GfmAutoComplete.Emoji.assetBase = '#{image_path("emoji")}'
+ GitLab.GfmAutoComplete.setup();
diff --git a/app/views/layouts/_page_title.html.haml b/app/views/layouts/_page_title.html.haml
index ea72b25add9..54da5074763 100644
--- a/app/views/layouts/_page_title.html.haml
+++ b/app/views/layouts/_page_title.html.haml
@@ -1,2 +1,2 @@
-- if content_for?(:page_title)
- = yield :page_title
+- if content_for?(:page-title)
+ = yield :page-title
diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml
new file mode 100644
index 00000000000..3c4bd857c22
--- /dev/null
+++ b/app/views/layouts/_public_head_panel.html.haml
@@ -0,0 +1,22 @@
+%header.navbar.navbar-static-top.navbar-gitlab
+ .navbar-inner
+ .container
+ %div.app_logo
+ %span.separator
+ = link_to public_root_path, class: "home" do
+ %h1 GITLAB
+ %span.separator
+ %h1.project_name
+ - if @project
+ = project_title(@project)
+ - else
+ Public Projects
+
+ %ul.nav
+ %li
+ %a
+ %div.hide.turbolink-spinner
+ %i.icon-refresh.icon-spin
+ Loading...
+ %li
+ = link_to "Sign in", new_session_path(:user), class: 'btn btn-sign-in'
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index c484af04704..9a0db99332a 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -1,13 +1,10 @@
.search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
- = text_field_tag "search", nil, placeholder: "Search", class: "search-input"
+ = text_field_tag "search", nil, placeholder: search_placeholder, class: "search-input"
= hidden_field_tag :group_id, @group.try(:id)
- = hidden_field_tag :project_id, @project.try(:id)
-
-:javascript
- $(function(){
- $("#search").autocomplete({
- source: #{raw search_autocomplete_source},
- select: function(event, ui) { location.href = ui.item.url }
- });
- });
+ - if @project && @project.persisted?
+ = hidden_field_tag :project_id, @project.id
+ = hidden_field_tag :search_code, true
+ = hidden_field_tag :repository_ref, @ref
+ = submit_tag 'Go' if ENV['RAILS_ENV'] == 'test'
+ .search-autocomplete-json.hide{:'data-autocomplete-opts' => search_autocomplete_source }
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index a01886cdabf..3a23cbdb376 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -1,27 +1,11 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Admin area"
- %body{class: "#{app_theme} admin"}
- = render "layouts/flash"
+ %body{class: "#{app_theme} admin", :'data-page' => body_data_page}
= render "layouts/head_panel", title: "Admin area"
- .container
- %ul.main_menu
- = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
- = link_to admin_root_path, title: "Stats" do
- %i.icon-home
- = nav_link(controller: :projects) do
- = link_to "Projects", admin_projects_path
- = nav_link(controller: :teams) do
- = link_to "Teams", admin_teams_path
- = nav_link(controller: :groups) do
- = link_to "Groups", admin_groups_path
- = nav_link(controller: :users) do
- = link_to "Users", admin_users_path
- = nav_link(controller: :logs) do
- = link_to "Logs", admin_logs_path
- = nav_link(controller: :hooks) do
- = link_to "Hooks", admin_hooks_path
- = nav_link(controller: :resque) do
- = link_to "Background Jobs", admin_resque_path
+ = render "layouts/flash"
+ %nav.main-nav
+ .container= render 'layouts/nav/admin'
+ .container
.content= yield
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 7ee44238d10..792fe5e4a28 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,28 +1,11 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Dashboard"
- %body{class: "#{app_theme} application"}
- = render "layouts/flash"
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page }
= render "layouts/head_panel", title: "Dashboard"
- .container
- %ul.main_menu
- = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
- = link_to root_path, title: "Home" do
- %i.icon-home
- = nav_link(path: 'dashboard#projects') do
- = link_to projects_dashboard_path do
- Projects
- = nav_link(path: 'dashboard#issues') do
- = link_to issues_dashboard_path do
- Issues
- %span.count= current_user.assigned_issues.opened.count
- = nav_link(path: 'dashboard#merge_requests') do
- = link_to merge_requests_dashboard_path do
- Merge Requests
- %span.count= current_user.cared_merge_requests.opened.count
- = nav_link(path: 'search#show') do
- = link_to "Search", search_path
- = nav_link(controller: :help) do
- = link_to "Help", help_path
+ = render "layouts/flash"
+ %nav.main-nav
+ .container= render 'layouts/nav/dashboard'
+ .container
.content= yield
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 36c6b4c6c35..c4729836faa 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -3,4 +3,13 @@
= render "layouts/head"
%body.ui_basic.login-page
= render "layouts/flash"
- .container= yield
+ .container
+ .content
+ %center
+ %h1 GitLab
+ %p.light
+ GitLab is open source software to collaborate on code.
+ %br
+ #{link_to "Sign in", new_user_session_path} or browse for #{link_to "public projects", public_projects_path}.
+ %hr
+ = yield
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index 3554d88f10c..df2350b1535 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -2,8 +2,8 @@
%html{ lang: "en"}
= render "layouts/head", title: "Error"
%body{class: "#{app_theme} application"}
+ = render "layouts/head_panel", title: "" if current_user
= render "layouts/flash"
- = render "layouts/head_panel", title: ""
.container
.content
%center.padded.prepend-top-20
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 9057ad50ce6..0e955d59ff8 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,31 +1,11 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "#{@group.name}"
- %body{class: "#{app_theme} application"}
- = render "layouts/flash"
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/head_panel", title: "group: #{@group.name}"
- .container
- %ul.main_menu
- = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
- = link_to group_path(@group), title: "Home" do
- %i.icon-home
- = 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
- = 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
- = nav_link(path: 'groups#search') do
- = link_to "Search", search_group_path(@group)
- = nav_link(path: 'groups#people') do
- = link_to "People", people_group_path(@group)
-
- - if can?(current_user, :manage_group, @group)
- = nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), class: "tab " do
- %i.icon-edit
- Edit Group
+ = render "layouts/flash"
+ %nav.main-nav
+ .container= render 'layouts/nav/group'
+ .container
.content= yield
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
new file mode 100644
index 00000000000..73946f99889
--- /dev/null
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -0,0 +1,17 @@
+%ul
+ = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
+ = link_to admin_root_path, title: "Stats" do
+ %i.icon-home
+ = nav_link(controller: :projects) do
+ = link_to "Projects", admin_projects_path
+ = nav_link(controller: :groups) do
+ = link_to "Groups", admin_groups_path
+ = nav_link(controller: :users) do
+ = link_to "Users", admin_users_path
+ = nav_link(controller: :logs) do
+ = link_to "Logs", admin_logs_path
+ = nav_link(controller: :hooks) do
+ = link_to "Hooks", admin_hooks_path
+ = nav_link(controller: :background_jobs) do
+ = link_to "Background Jobs", admin_background_jobs_path
+
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
new file mode 100644
index 00000000000..cae24c3170d
--- /dev/null
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -0,0 +1,18 @@
+%ul
+ = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
+ = link_to root_path, title: "Home" do
+ %i.icon-home
+ = nav_link(path: 'dashboard#projects') do
+ = link_to projects_dashboard_path do
+ Projects
+ = nav_link(path: 'dashboard#issues') do
+ = link_to issues_dashboard_path do
+ Issues
+ %span.count= current_user.assigned_issues.opened.count
+ = nav_link(path: 'dashboard#merge_requests') do
+ = link_to merge_requests_dashboard_path do
+ Merge Requests
+ %span.count= current_user.cared_merge_requests.opened.count
+ = nav_link(controller: :help) do
+ = link_to "Help", help_path
+
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
new file mode 100644
index 00000000000..a8bb3b91559
--- /dev/null
+++ b/app/views/layouts/nav/_group.html.haml
@@ -0,0 +1,20 @@
+%ul
+ = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
+ = link_to group_path(@group), title: "Home" do
+ %i.icon-home
+ = 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
+ = 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
+ = nav_link(path: 'groups#members') do
+ = link_to "Members", members_group_path(@group)
+
+ - if can?(current_user, :manage_group, @group)
+ = nav_link(path: 'groups#edit') do
+ = link_to edit_group_path(@group), class: "tab " do
+ Settings
+
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
new file mode 100644
index 00000000000..7c3acfc398a
--- /dev/null
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -0,0 +1,19 @@
+%ul
+ = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
+ = link_to profile_path, title: "Profile" do
+ %i.icon-home
+ = nav_link(path: 'profiles#account') do
+ = link_to "Account", account_profile_path
+ = nav_link(controller: :notifications) do
+ = link_to "Notifications", profile_notifications_path
+ = nav_link(controller: :keys) do
+ = link_to profile_keys_path do
+ SSH Keys
+ %span.count= current_user.keys.count
+ = nav_link(path: 'profiles#design') do
+ = link_to "Design", design_profile_path
+ = nav_link(controller: :groups) do
+ = link_to "Groups", profile_groups_path
+ = nav_link(path: 'profiles#history') do
+ = link_to "History", history_profile_path
+
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
new file mode 100644
index 00000000000..e9d535f6972
--- /dev/null
+++ b/app/views/layouts/nav/_project.html.haml
@@ -0,0 +1,50 @@
+%ul
+ = nav_link(path: 'projects#show', html_options: {class: "home"}) do
+ = link_to project_path(@project), title: "Project" do
+ %i.icon-home
+
+ - if project_nav_tab? :files
+ = nav_link(controller: %w(tree blob blame edit_tree)) do
+ = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref)
+
+ - if project_nav_tab? :commits
+ = nav_link(controller: %w(commit commits compare repositories protected_branches tags branches)) do
+ = link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref)
+
+ - if project_nav_tab? :network
+ = nav_link(controller: %w(network)) do
+ = link_to "Network", project_network_path(@project, @ref || @repository.root_ref)
+
+ - if project_nav_tab? :graphs
+ = nav_link(controller: %w(graphs)) do
+ = link_to "Graphs", project_graph_path(@project, @ref || @repository.root_ref)
+
+ - if project_nav_tab? :issues
+ = nav_link(controller: %w(issues milestones labels)) do
+ = link_to url_for_project_issues do
+ Issues
+ - if @project.used_default_issues_tracker?
+ %span.count.issue_counter= @project.issues.opened.count
+
+ - if project_nav_tab? :merge_requests
+ = nav_link(controller: :merge_requests) do
+ = link_to project_merge_requests_path(@project) do
+ Merge Requests
+ %span.count.merge_counter= @project.merge_requests.opened.count
+
+ - if project_nav_tab? :wiki
+ = nav_link(controller: :wikis) do
+ = link_to 'Wiki', project_wiki_path(@project, :home)
+
+ - if project_nav_tab? :wall
+ = nav_link(controller: :walls) do
+ = link_to 'Wall', project_wall_path(@project)
+
+ - if project_nav_tab? :snippets
+ = nav_link(controller: :snippets) do
+ = link_to 'Snippets', project_snippets_path(@project)
+
+ - if project_nav_tab? :settings
+ = nav_link(html_options: {class: "#{project_tab_class}"}) do
+ = link_to edit_project_path(@project), class: "stat-tab tab " do
+ Settings
diff --git a/app/views/layouts/navless.html.haml b/app/views/layouts/navless.html.haml
new file mode 100644
index 00000000000..7325664bc19
--- /dev/null
+++ b/app/views/layouts/navless.html.haml
@@ -0,0 +1,10 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head", title: @title
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/head_panel", title: @title
+ = render "layouts/flash"
+
+ .container.navless-container
+ .content
+ = yield
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 3db1f59b54d..f88abeca887 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -4,29 +4,19 @@
%title
GitLab
- %body{bgcolor: "#EAEAEA", style: "margin: 0; padding: 0; background: #EAEAEA"}
- %table{align: "center", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 35px 0; background: #EAEAEA;", width: "100%"}
+ %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: "center", style: "margin: 0; padding: 0; background: #EAEAEA;"}
- %table.header{align: "center", border: "0", cellpadding: "0", cellspacing: "0", style: "font-family: Helvetica, Arial, sans-serif; background:#333", width: "600"}
- %tr
- %td{style: "font-size: 0px;", width: "20"}
- \ 
- %td{align: "left", style: "padding: 10px 0", width: "580"}
- %h1{style: "color: #BBBBBB; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 32px;"}
- GITLAB
- - if @project
- \/ #{@project.name_with_namespace}
- %table{align: "center", bgcolor: "#fff", border: "0", cellpadding: "0", cellspacing: "0", style: "font-family: Helvetica, Arial, sans-serif; background: #fff;", width: "600"}
- %tr= yield
- %tr
- %td{align: "left", colspan: "2", height: "3", style: "padding: font-size: 0; line-height: 0; height: 3px;", width: "600"}
- %table.footer{align: "center", border: "0", cellpadding: "0", cellspacing: "0", style: "font-family: Helvetica, Arial, sans-serif; line-height: 10px;", width: "600"}
- %tr
- %td{align: "center", style: "padding: 5px 0 10px; font-size: 11px; color:#7d7a7a; margin: 0; line-height: 1.2;font-family: Helvetica, Arial, sans-serif;", valign: "top"}
- %br
- %p{style: "font-size: 11px; color:#7d7a7a; margin: 0; padding: 0; font-family: Helvetica, Arial, sans-serif;"}
- You're receiving this notification because you are a member of the
- - if @project
- #{@project.name_with_namespace}
- project team.
+ %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.
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 57f250c775b..30a0532bc2b 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,23 +1,11 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Profile"
- %body{class: "#{app_theme} profile"}
- = render "layouts/flash"
+ %body{class: "#{app_theme} profile", :'data-page' => body_data_page}
= render "layouts/head_panel", title: "Profile"
- .container
- %ul.main_menu
- = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
- = link_to profile_path, title: "Profile" do
- %i.icon-home
- = nav_link(path: 'profiles#account') do
- = link_to "Account", account_profile_path
- = nav_link(controller: :keys) do
- = link_to keys_path do
- SSH Keys
- %span.count= current_user.keys.count
- = nav_link(path: 'profiles#design') do
- = link_to "Design", design_profile_path
- = nav_link(path: 'profiles#history') do
- = link_to "History", history_profile_path
+ = render "layouts/flash"
+ %nav.main-nav
+ .container= render 'layouts/nav/profile'
+ .container
.content= yield
diff --git a/app/views/layouts/project_resource.html.haml b/app/views/layouts/project_resource.html.haml
deleted file mode 100644
index 13fb8637bf6..00000000000
--- a/app/views/layouts/project_resource.html.haml
+++ /dev/null
@@ -1,45 +0,0 @@
-!!! 5
-%html{ lang: "en"}
- = render "layouts/head", title: @project.name_with_namespace
- %body{class: "#{app_theme} project"}
- = render "layouts/flash"
- = render "layouts/head_panel", title: project_title(@project)
- - if can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
-
- .container
- %ul.main_menu
- = nav_link(html_options: {class: "home #{project_tab_class}"}) do
- = link_to project_path(@project), title: "Project" do
- %i.icon-home
-
- - if @project.repo_exists?
- - if can? current_user, :download_code, @project
- = nav_link(controller: %w(tree blob blame)) do
- = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref)
- = nav_link(controller: %w(commit commits compare repositories protected_branches)) do
- = link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref)
- = nav_link(controller: %w(graph)) do
- = link_to "Network", project_graph_path(@project, @ref || @repository.root_ref)
-
- - if @project.issues_enabled
- = nav_link(controller: %w(issues milestones labels)) do
- = link_to project_issues_filter_path(@project) do
- Issues
- %span.count.issue_counter= @project.issues.opened.count
-
- - if @project.repo_exists? && @project.merge_requests_enabled
- = nav_link(controller: :merge_requests) do
- = link_to project_merge_requests_path(@project) do
- Merge Requests
- %span.count.merge_counter= @project.merge_requests.opened.count
-
- - if @project.wall_enabled
- = nav_link(path: 'projects#wall') do
- = link_to 'Wall', wall_project_path(@project)
-
- - if @project.wiki_enabled
- = nav_link(controller: :wikis) do
- = link_to 'Wiki', project_wiki_path(@project, :index)
-
- .content= yield
diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml
new file mode 100644
index 00000000000..ea739da73d8
--- /dev/null
+++ b/app/views/layouts/project_settings.html.haml
@@ -0,0 +1,20 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head", title: @project.name_with_namespace
+ %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
+ = render "layouts/head_panel", title: project_title(@project)
+ = render "layouts/init_auto_complete"
+ = render "layouts/flash"
+ - if can?(current_user, :download_code, @project)
+ = render 'shared/no_ssh'
+
+ %nav.main-nav
+ .container= render 'layouts/nav/project'
+
+ .container
+ .content
+ .row
+ .span2
+ = render "projects/settings_nav"
+ .span10
+ = yield
diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml
new file mode 100644
index 00000000000..6d8bf9b710b
--- /dev/null
+++ b/app/views/layouts/projects.html.haml
@@ -0,0 +1,15 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head", title: @project.name_with_namespace
+ %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
+ = render "layouts/head_panel", title: project_title(@project)
+ = render "layouts/init_auto_complete"
+ = render "layouts/flash"
+ - if can?(current_user, :download_code, @project)
+ = render 'shared/no_ssh'
+
+ %nav.main-nav
+ .container= render 'layouts/nav/project'
+
+ .container
+ .content= yield
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index fa368e9be2e..f922dcc4203 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -1,17 +1,11 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Public Projects"
- %body{class: "#{app_theme} application"}
- %header.navbar.navbar-static-top.navbar-gitlab
- .navbar-inner
- .container
- %div.app_logo
- %span.separator
- = link_to root_path, class: "home" do
- %h1 GITLAB
- %span.separator
- %h1.project_name Public Projects
- .container
- .content
- .prepend-top-20
- = yield
+ %body{class: "ui_mars application", :'data-page' => body_data_page}
+ - if current_user
+ = render "layouts/head_panel", title: "Public Projects"
+ - else
+ = render "layouts/public_head_panel"
+
+ .container.navless-container
+ .content= yield
diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml
new file mode 100644
index 00000000000..cfe6a63055a
--- /dev/null
+++ b/app/views/layouts/public_projects.html.haml
@@ -0,0 +1,9 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head", title: @project.name_with_namespace
+ %body{class: "ui_mars application", :'data-page' => body_data_page}
+ = render "layouts/public_head_panel"
+ %nav.main-nav
+ .container= render 'layouts/nav/project'
+ .container
+ .content= yield
diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml
new file mode 100644
index 00000000000..177b3a4f8f4
--- /dev/null
+++ b/app/views/layouts/search.html.haml
@@ -0,0 +1,10 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head", title: "Search"
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/head_panel", title: "Search"
+ = render "layouts/flash"
+
+ .container.navless-container
+ .content
+ = yield
diff --git a/app/views/layouts/user_team.html.haml b/app/views/layouts/user_team.html.haml
index 19bbc373f46..e64e68d2446 100644
--- a/app/views/layouts/user_team.html.haml
+++ b/app/views/layouts/user_team.html.haml
@@ -1,39 +1,11 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "#{@team.name}"
- %body{class: "#{app_theme} application"}
- = render "layouts/flash"
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/head_panel", title: "team: #{@team.name}"
- .container
- %ul.main_menu
- = nav_link(path: 'teams#show', html_options: {class: 'home'}) do
- = link_to team_path(@team), title: "Home" do
- %i.icon-home
-
- = nav_link(path: 'teams#issues') do
- = link_to issues_team_path(@team) do
- Issues
- %span.count= Issue.opened.of_user_team(@team).count
-
- = nav_link(path: 'teams#merge_requests') do
- = link_to merge_requests_team_path(@team) do
- Merge Requests
- %span.count= MergeRequest.opened.of_user_team(@team).count
-
- = nav_link(controller: [:members]) do
- = link_to team_members_path(@team), class: "team-tab tab" do
- Members
- %span.count= @team.members.count
-
- - if can? current_user, :admin_user_team, @team
- = nav_link(controller: [:projects]) do
- = link_to team_projects_path(@team), class: "team-tab tab" do
- Projects
- %span.count= @team.projects.count
-
- = nav_link(path: 'teams#edit') do
- = link_to edit_team_path(@team), class: "stat-tab tab " do
- %i.icon-edit
- Edit Team
+ = render "layouts/flash"
+ %nav.main-nav
+ .container= render 'layouts/nav/team'
+ .container
.content= yield
diff --git a/app/views/merge_requests/_filter.html.haml b/app/views/merge_requests/_filter.html.haml
deleted file mode 100644
index 4b48306ed05..00000000000
--- a/app/views/merge_requests/_filter.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-= form_tag project_issues_path(@project), method: 'get' do
- %fieldset
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if (params[:f] == 'open' || !params[:f]))}
- = link_to project_merge_requests_path(@project, f: 'open', milestone_id: params[:milestone_id]) do
- Open
- %li{class: ("active" if params[:f] == "closed")}
- = link_to project_merge_requests_path(@project, f: "closed", milestone_id: params[:milestone_id]) do
- Closed
- %li{class: ("active" if params[:f] == 'assigned-to-me')}
- = link_to project_merge_requests_path(@project, f: 'assigned-to-me', milestone_id: params[:milestone_id]) do
- Assigned To Me
- %li{class: ("active" if params[:f] == 'all')}
- = link_to project_merge_requests_path(@project, f: 'all', milestone_id: params[:milestone_id]) do
- All
-
- %fieldset
- %hr
- = link_to "Reset", project_merge_requests_path(@project), class: 'btn pull-right'
-
diff --git a/app/views/merge_requests/_form.html.haml b/app/views/merge_requests/_form.html.haml
deleted file mode 100644
index 816c852d24b..00000000000
--- a/app/views/merge_requests/_form.html.haml
+++ /dev/null
@@ -1,81 +0,0 @@
-= form_for [@project, @merge_request], html: { class: "#{controller.action_name}-merge-request form-horizontal" } do |f|
- -if @merge_request.errors.any?
- .alert.alert-error
- %ul
- - @merge_request.errors.full_messages.each do |msg|
- %li= msg
-
- %fieldset
- %legend 1. Select Branches
-
- .row
- .span5
- .mr_branch_box
- %h5.cgray From (Head Branch)
- .body
- .padded= f.select(:source_branch, @repository.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span4'})
- .mr_source_commit
-
- .span2
- %center= image_tag "merge.png", class: 'mr_direction_tip'
- .span5
- .mr_branch_box
- %h5.cgray To (Base Branch)
- .body
- .padded= f.select(:target_branch, @repository.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span4'})
- .mr_target_commit
-
- %fieldset
- %legend 2. Fill info
-
- .ui-box.ui-box-show
- .ui-box-head
- .clearfix
- = f.label :title do
- %strong= "Title *"
- .input= f.text_field :title, class: "input-xxlarge pad js-gfm-input", maxlength: 255, rows: 5, required: true
- .ui-box-body
- .clearfix
- .left
- = f.label :assignee_id do
- %i.icon-user
- Assign to
- .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'})
- .left
- = f.label :milestone_id do
- %i.icon-time
- Milestone
- .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
-
- .control-group
-
- .form-actions
- - if @merge_request.new_record?
- = f.submit 'Submit merge request', class: "btn btn-create"
- -else
- = f.submit 'Save changes', class: "btn btn-save"
- - if @merge_request.new_record?
- = link_to project_merge_requests_path(@project), class: "btn btn-cancel" do
- Cancel
- - else
- = link_to project_merge_request_path(@project, @merge_request), class: "btn btn-cancel" do
- Cancel
-
-:javascript
- $(function(){
- disableButtonIfEmptyField("#merge_request_title", ".btn-save");
-
- var source_branch = $("#merge_request_source_branch")
- , target_branch = $("#merge_request_target_branch");
-
- $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: source_branch.val() });
- $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: target_branch.val() });
-
- source_branch.live("change", function() {
- $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: $(this).val() });
- });
-
- target_branch.live("change", function() {
- $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: $(this).val() });
- });
- });
diff --git a/app/views/merge_requests/_merge_request.html.haml b/app/views/merge_requests/_merge_request.html.haml
deleted file mode 100644
index 09c55d98465..00000000000
--- a/app/views/merge_requests/_merge_request.html.haml
+++ /dev/null
@@ -1,32 +0,0 @@
-%li{ class: mr_css_classes(merge_request) }
- .pull-right
- .left
- - if merge_request.merged?
- %span.btn.btn-small.disabled.grouped
- %strong
- %i.icon-ok
- = "MERGED"
- - if merge_request.notes.any?
- %span.btn.btn-small.disabled.grouped
- %i.icon-comment
- = merge_request.mr_and_commit_notes.count
- - if merge_request.milestone_id?
- %span.btn.btn-small.disabled.grouped
- %i.icon-time
- = merge_request.milestone.title
- %span.btn.btn-small.disabled.grouped
- = merge_request.source_branch
- &rarr;
- = merge_request.target_branch
- = image_tag gravatar_icon(merge_request.author_email), class: "avatar"
-
- %p= link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.project, merge_request), class: "row_title"
-
- %span.update-author
- %small.cdark= "##{merge_request.id}"
- authored by #{merge_request.author_name}
- = time_ago_in_words(merge_request.created_at)
- ago
-
- - if merge_request.votes_count > 0
- = render 'votes/votes_inline', votable: merge_request
diff --git a/app/views/merge_requests/_show.html.haml b/app/views/merge_requests/_show.html.haml
deleted file mode 100644
index cefd33c0cdf..00000000000
--- a/app/views/merge_requests/_show.html.haml
+++ /dev/null
@@ -1,39 +0,0 @@
-.merge-request
- = render "merge_requests/show/mr_title"
- = render "merge_requests/show/how_to_merge"
- = render "merge_requests/show/mr_box"
- = render "merge_requests/show/mr_accept"
- - if @project.gitlab_ci?
- = render "merge_requests/show/mr_ci"
- = render "merge_requests/show/commits"
-
- - if @commits.present?
- %ul.nav.nav-tabs
- %li.notes-tab{data: {action: 'notes'}}
- = link_to project_merge_request_path(@project, @merge_request) do
- %i.icon-comment
- Discussion
- %li.diffs-tab{data: {action: 'diffs'}}
- = link_to diffs_project_merge_request_path(@project, @merge_request) do
- %i.icon-list-alt
- Diff
-
- .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
- = render "notes/notes_with_form"
- .diffs.tab-content
- = render "merge_requests/show/diffs" if @diffs
- .status
-
-:javascript
- var merge_request;
- $(function(){
- merge_request = new MergeRequest({
- url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}",
- check_enable: #{@merge_request.state == MergeRequest::UNCHECKED ? "true" : "false"},
- url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}",
- ci_enable: #{@project.gitlab_ci? ? "true" : "false"},
- current_state: "#{@merge_request.human_state}",
- action: "#{controller.action_name}"
- });
- });
-
diff --git a/app/views/merge_requests/branch_from.js.haml b/app/views/merge_requests/branch_from.js.haml
deleted file mode 100644
index 0637fdcb72e..00000000000
--- a/app/views/merge_requests/branch_from.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
- $(".mr_source_commit").html("#{commit_to_html(@commit)}");
diff --git a/app/views/merge_requests/branch_to.js.haml b/app/views/merge_requests/branch_to.js.haml
deleted file mode 100644
index 974100d1ba7..00000000000
--- a/app/views/merge_requests/branch_to.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
- $(".mr_target_commit").html("#{commit_to_html(@commit)}");
diff --git a/app/views/merge_requests/edit.html.haml b/app/views/merge_requests/edit.html.haml
deleted file mode 100644
index eee148994d7..00000000000
--- a/app/views/merge_requests/edit.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-%h3.page_title
- = "Edit merge request #{@merge_request.id}"
-%hr
-= render 'form'
diff --git a/app/views/merge_requests/index.html.haml b/app/views/merge_requests/index.html.haml
deleted file mode 100644
index 3073c8f6bab..00000000000
--- a/app/views/merge_requests/index.html.haml
+++ /dev/null
@@ -1,35 +0,0 @@
-- if can? current_user, :write_merge_request, @project
- = link_to new_project_merge_request_path(@project), class: "pull-right btn btn-primary", title: "New Merge Request" do
- %i.icon-plus
- New Merge Request
-%h3.page_title
- Merge Requests
-
-%br
-
-
-.row
- .span3
- = render 'filter'
- .span9
- .ui-box
- .title
- = form_tag project_merge_requests_path(@project), id: "merge_requests_search_form", method: :get, class: :left do
- = select_tag(:assignee_id, options_from_collection_for_select([unassigned_filter] + @project.users.all, "id", "name", params[:assignee_id]), prompt: "Assignee")
- = select_tag(:milestone_id, options_from_collection_for_select([unassigned_filter] + @project.milestones.order("id desc").all, "id", "title", params[:milestone_id]), prompt: "Milestone")
- = hidden_field_tag :f, params[:f]
- .clearfix
-
- %ul.well-list
- = render @merge_requests
- - if @merge_requests.blank?
- %li
- %h4.nothing_here_message Nothing to show here
- - if @merge_requests.present?
- %li.bottom
- .left= paginate @merge_requests, theme: "gitlab"
- .pull-right
- %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter
-
-:javascript
- $(merge_requestsPage);
diff --git a/app/views/merge_requests/new.html.haml b/app/views/merge_requests/new.html.haml
deleted file mode 100644
index 594089995ea..00000000000
--- a/app/views/merge_requests/new.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-%h3.page_title New Merge Request
-%hr
-= render 'form'
diff --git a/app/views/merge_requests/show/_diffs.html.haml b/app/views/merge_requests/show/_diffs.html.haml
deleted file mode 100644
index 0807454c4b0..00000000000
--- a/app/views/merge_requests/show/_diffs.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-- if @merge_request.valid_diffs?
- = render "commits/diffs", diffs: @diffs
-- elsif @merge_request.broken_diffs?
- %h4.nothing_here_message
- Can't load diff.
- You can
- = link_to "download it", project_merge_request_path(@project, @merge_request), format: :diff, class: "vlink"
- instead.
-- else
- %h4.nothing_here_message Nothing to merge
diff --git a/app/views/merge_requests/show/_how_to_merge.html.haml b/app/views/merge_requests/show/_how_to_merge.html.haml
deleted file mode 100644
index 69881d4352f..00000000000
--- a/app/views/merge_requests/show/_how_to_merge.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-%div#modal_merge_info.modal.hide
- .modal-header
- %a.close{href: "#"} ×
- %h3 How To Merge
- .modal-body
- %pre.dark
- = preserve do
- git checkout #{@merge_request.target_branch}
- git fetch origin
- git merge origin/#{@merge_request.source_branch}
- git push origin #{@merge_request.target_branch}
-
-
-:javascript
- $(function(){
- var modal = $('#modal_merge_info').modal({modal: true, show:false});
- $('.how_to_merge_link').bind("click", function(){
- modal.show();
- });
- $('.modal-header .close').bind("click", function(){
- modal.hide();
- })
- })
-
diff --git a/app/views/merge_requests/show/_mr_box.html.haml b/app/views/merge_requests/show/_mr_box.html.haml
deleted file mode 100644
index 644d7fcc58e..00000000000
--- a/app/views/merge_requests/show/_mr_box.html.haml
+++ /dev/null
@@ -1,34 +0,0 @@
-.ui-box.ui-box-show
- .ui-box-head
- %h4.box-title
- - if @merge_request.merged
- .error.status_info
- %i.icon-ok
- Merged
- - elsif @merge_request.closed
- .error.status_info Closed
- = gfm escape_once(@merge_request.title)
-
- .ui-box-body
- %div
- %cite.cgray
- Created at #{@merge_request.created_at.stamp("Aug 21, 2011")} 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
- - milestone = @merge_request.milestone
- %cite.cgray and attached to milestone
- %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
-
-
- - if @merge_request.closed
- .ui-box-bottom
- - if @merge_request.merged?
- %span
- Merged by #{link_to_member(@project, @merge_request.merge_event.author)}
- %small #{time_ago_in_words(@merge_request.merge_event.created_at)} ago.
- - elsif @merge_request.closed_event
- %span
- Closed by #{link_to_member(@project, @merge_request.closed_event.author)}
- %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago.
-
diff --git a/app/views/milestones/show.html.haml b/app/views/milestones/show.html.haml
deleted file mode 100644
index 43d82a54dd6..00000000000
--- a/app/views/milestones/show.html.haml
+++ /dev/null
@@ -1,94 +0,0 @@
-.row
- .span6
- %h3.page_title
- Milestone ##{@milestone.id}
- %small
- = @milestone.expires_at
- .back_link
- = link_to project_milestones_path(@project) do
- &larr; To milestones list
- .span6
- .pull-right
- - unless @milestone.closed
- = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small 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 edit-milestone-link small grouped"
- - if can?(current_user, :admin_milestone, @project)
- = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-small grouped" do
- %i.icon-edit
- Edit
-
-
-
-- if @milestone.can_be_closed?
- %hr
- %p
- %span All issues for this milestone are closed. You may close milestone now.
- = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {closed: true }), method: :put, class: "btn btn-small btn-remove"
-
-.ui-box.ui-box-show
- .ui-box-head
- %h4.box-title
- - if @milestone.closed
- .error.status_info Closed
- - elsif @milestone.expired?
- .error.status_info Expired
-
- = gfm escape_once(@milestone.title)
-
- .ui-box-body
- %p
- Progress:
- #{@milestone.closed_items_count} closed
- &ndash;
- #{@milestone.open_items_count} open
- %span.pull-right= @milestone.expires_at
- .progress.progress-info
- .bar{style: "width: #{@milestone.percent_complete}%;"}
-
-
- - if @milestone.description.present?
- .ui-box-bottom
- = preserve do
- = markdown @milestone.description
-
-
-.row
- .span6
- .ui-box.milestone-issue-filter
- .title
- %ul.nav.nav-pills
- %li.active= link_to('Open Issues', '#')
- %li=link_to('All Issues', '#')
- %ul.well-list
- - @issues.each do |issue|
- %li{data: {closed: issue.closed}}
- = link_to [@project, issue] do
- %span.badge.badge-info ##{issue.id}
- &ndash;
- = link_to_gfm truncate(issue.title, length: 60), [@project, issue]
-
- .span6
- .ui-box.milestone-merge-requests-filter
- .title
- %ul.nav.nav-pills
- %li.active= link_to('Open Merge Requests', '#')
- %li=link_to('All Merge Requests', '#')
- %ul.well-list
- - @merge_requests.each do |merge_request|
- %li{data: {closed: merge_request.closed}}
- = link_to [@project, merge_request] do
- %span.badge.badge-info ##{merge_request.id}
- &ndash;
- = link_to_gfm truncate(merge_request.title, length: 60), [@project, merge_request]
-
-%hr
-%h6 Participants:
-%div
- - @users.each do |user|
- = link_to tm_path(user.tm_of(@project)), class: 'float-link' do
- = user.avatar_image
- = user.name
-
-.clearfix
diff --git a/app/views/notes/_diff_notes_with_reply.html.haml b/app/views/notes/_diff_notes_with_reply.html.haml
deleted file mode 100644
index 0808f86b090..00000000000
--- a/app/views/notes/_diff_notes_with_reply.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-- note = notes.first # example note
-%tr.notes_holder
- %td.notes_line{ colspan: 2 }
- %span.btn.disabled
- %i.icon-comment
- = notes.count
- %td.notes_content
- %ul.notes{ rel: note.discussion_id }
- = render notes
-
- = render "notes/discussion_reply_button", note: note
diff --git a/app/views/notes/_note.html.haml b/app/views/notes/_note.html.haml
deleted file mode 100644
index 4d3007a0ed1..00000000000
--- a/app/views/notes/_note.html.haml
+++ /dev/null
@@ -1,37 +0,0 @@
-%li{ id: dom_id(note), class: dom_class(note), data: { discussion: note.discussion_id } }
- .note-header
- .note-actions
- = link_to "##{dom_id(note)}", name: dom_id(note) do
- %i.icon-link
- Link here
- &nbsp;
- - if(note.author_id == current_user.id) || can?(current_user, :admin_note, @project)
- = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove comment?', remote: true, class: "danger js-note-delete" do
- %i.icon-trash.cred
- = image_tag gravatar_icon(note.author.email), class: "avatar s32"
- = link_to_member(@project, note.author, avatar: false)
- %span.note-last-update
- = time_ago_in_words(note.updated_at)
- ago
-
- - if note.upvote?
- %span.vote.upvote.label.label-success
- %i.icon-thumbs-up
- \+1
- - if note.downvote?
- %span.vote.downvote.label.label-error
- %i.icon-thumbs-down
- \-1
-
-
- .note-body
- = preserve do
- = markdown(note.note)
- - if note.attachment.url
- - if note.attachment.image?
- = image_tag note.attachment.url, class: 'note-image-attach'
- .attachment.pull-right
- = link_to note.attachment.url, target: "_blank" do
- %i.icon-paper-clip
- = note.attachment_identifier
- .clear
diff --git a/app/views/notes/_notes_with_form.html.haml b/app/views/notes/_notes_with_form.html.haml
deleted file mode 100644
index 2566edd81ad..00000000000
--- a/app/views/notes/_notes_with_form.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-%ul#notes-list.notes
-.js-notes-busy
-
-.js-main-target-form
-- if can? current_user, :write_note, @project
- = render "notes/form"
-
-:javascript
- $(function(){
- NoteList.init("#{@target_id}", "#{@target_type}", "#{project_notes_path(@project)}");
- });
diff --git a/app/views/notes/_reversed_notes_with_form.html.haml b/app/views/notes/_reversed_notes_with_form.html.haml
deleted file mode 100644
index bb583b8c3b8..00000000000
--- a/app/views/notes/_reversed_notes_with_form.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-.js-main-target-form
-- if can? current_user, :write_note, @project
- = render "notes/form"
-
-%ul#new-notes-list.reversed.notes
-%ul#notes-list.reversed.notes
-.notes-busy.js-notes-busy
-
-:javascript
- $(function(){
- NoteList.init("#{@target_id}", "#{@target_type}", "#{project_notes_path(@project)}");
- });
diff --git a/app/views/notes/index.js.haml b/app/views/notes/index.js.haml
deleted file mode 100644
index f0826100fbf..00000000000
--- a/app/views/notes/index.js.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-- unless @notes.blank?
- var notesHtml = "#{escape_javascript(render 'notes/notes')}";
- - new_note_ids = @notes.map(&:id)
- - if loading_more_notes?
- NoteList.appendMoreNotes(#{new_note_ids}, notesHtml);
-
- - elsif loading_new_notes?
- NoteList.replaceNewNotes(#{new_note_ids}, notesHtml);
-
- - else
- NoteList.setContent(#{new_note_ids}, notesHtml);
-
-- else
- - if loading_more_notes?
- NoteList.finishedLoadingMore();
diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml
new file mode 100644
index 00000000000..88c4df55b7f
--- /dev/null
+++ b/app/views/notify/_note_message.html.haml
@@ -0,0 +1,6 @@
+%p
+ %strong #{@note.author_name}
+ left next message:
+
+%cite{style: 'color: #666'}
+ = markdown(@note.note)
diff --git a/app/views/notify/closed_issue_email.html.haml b/app/views/notify/closed_issue_email.html.haml
new file mode 100644
index 00000000000..325cd44eb4b
--- /dev/null
+++ b/app/views/notify/closed_issue_email.html.haml
@@ -0,0 +1,5 @@
+%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_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml
new file mode 100644
index 00000000000..49f160a0d5f
--- /dev/null
+++ b/app/views/notify/closed_issue_email.text.haml
@@ -0,0 +1,3 @@
+= "Issue was closed by #{@updated_by.name}"
+
+Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)}
diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml
new file mode 100644
index 00000000000..45770cc85de
--- /dev/null
+++ b/app/views/notify/closed_merge_request_email.html.haml
@@ -0,0 +1,9 @@
+%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}
+
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
new file mode 100644
index 00000000000..ee434ec8cb2
--- /dev/null
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -0,0 +1,8 @@
+= "Merge Request #{@merge_request.iid} was closed by #{@updated_by.name}"
+
+Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+
+= merge_path_description(@merge_request, 'to')
+
+Author: #{@merge_request.author_name}
+Assignee: #{@merge_request.assignee_name}
diff --git a/app/views/notify/group_access_granted_email.html.haml b/app/views/notify/group_access_granted_email.html.haml
new file mode 100644
index 00000000000..5023ec737a5
--- /dev/null
+++ b/app/views/notify/group_access_granted_email.html.haml
@@ -0,0 +1,5 @@
+%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/group_access_granted_email.text.erb b/app/views/notify/group_access_granted_email.text.erb
new file mode 100644
index 00000000000..331bb98d5c9
--- /dev/null
+++ b/app/views/notify/group_access_granted_email.text.erb
@@ -0,0 +1,4 @@
+
+You have been granted <%= @membership.human_access %> access to group <%= @group.name %>
+
+<%= url_for(group_url(@group)) %>
diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml
index 27168eef742..7706b3f7516 100644
--- a/app/views/notify/issue_status_changed_email.html.haml
+++ b/app/views/notify/issue_status_changed_email.html.haml
@@ -1,16 +1,5 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{width: "21"}
- %td
- %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
- = "Issue was #{@issue_status} by #{@updated_by.name}"
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{align: "left", style: "padding: 20px 0 0;"}
- %p{style: "color:#646464 !important; line-height: 26px; font-size: 16px; font-family: Helvetica, Arial, sans-serif; "}
- = "Issue ##{@issue.id}"
- = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
- %br
-
+%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/issue_status_changed_email.text.erb b/app/views/notify/issue_status_changed_email.text.erb
new file mode 100644
index 00000000000..4200881f7e8
--- /dev/null
+++ b/app/views/notify/issue_status_changed_email.text.erb
@@ -0,0 +1,4 @@
+Issue was <%= @issue_status %> by <%= @updated_by.name %>
+
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
+
diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml
new file mode 100644
index 00000000000..e2bc9cf5c04
--- /dev/null
+++ b/app/views/notify/merged_merge_request_email.html.haml
@@ -0,0 +1,9 @@
+%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}
+
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
new file mode 100644
index 00000000000..550f677fed4
--- /dev/null
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -0,0 +1,8 @@
+= "Merge Request #{@merge_request.iid} was merged"
+
+Merge Request Url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+
+= merge_path_description(@merge_request, 'to')
+
+Author: #{@merge_request.author_name}
+Assignee: #{@merge_request.assignee_name}
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index 3cb5351319e..3b3c148517a 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -1,15 +1,9 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{width: "21"}
- %td
- %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
- New Issue was created and assigned to you.
- %td{width: "21"}
- %tr
- %td{width: "21"}
- %td{align: "left", style: "padding: 20px 0 0;"}
- %p{style: "color:#646464 !important; line-height: 26px; font-size: 16px; font-family: Helvetica, Arial, sans-serif; "}
- = "Issue ##{@issue.id}"
- = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
- %br
+%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}
diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
new file mode 100644
index 00000000000..d36f54eb1ca
--- /dev/null
+++ b/app/views/notify/new_issue_email.text.erb
@@ -0,0 +1,5 @@
+New Issue was created.
+
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
+Author: <%= @issue.author_name %>
+Asignee: <%= @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 990d4d2aa87..6b2782ebb0b 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -1,19 +1,9 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{align: "left", style: "padding: 20px 0 0;"}
- %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
- = "New Merge Request !#{@merge_request.id}"
- %p{style: "color:#646464 !important; line-height: 26px; font-size: 16px; font-family: Helvetica, Arial, sans-serif; "}
- = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.project, @merge_request)
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{style: "padding: 15px 0 15px;", valign: "top"}
- %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
- Branches: #{@merge_request.source_branch} &rarr; #{@merge_request.target_branch}
- %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
- Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
- %td
+%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
+ != merge_path_description(@merge_request, '&rarr;')
+%p
+ Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
new file mode 100644
index 00000000000..2d27350486e
--- /dev/null
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -0,0 +1,8 @@
+New Merge Request <%= @merge_request.iid %>
+
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
+
+<%= merge_path_description(@merge_request, 'to') %>
+Author: <%= @merge_request.author_name %>
+Asignee: <%= @merge_request.assignee_name %>
+
diff --git a/app/views/notify/new_ssh_key_email.html.haml b/app/views/notify/new_ssh_key_email.html.haml
new file mode 100644
index 00000000000..deb0822d8f2
--- /dev/null
+++ b/app/views/notify/new_ssh_key_email.html.haml
@@ -0,0 +1,10 @@
+%p
+ Hi #{@user.name}!
+%p
+ A new public key was added to your account:
+%p
+ title:
+ %code= @key.title
+%p
+ If this key was added in error, you can remove it here:
+ = link_to "SSH Keys", profile_keys_url
diff --git a/app/views/notify/new_ssh_key_email.text.erb b/app/views/notify/new_ssh_key_email.text.erb
new file mode 100644
index 00000000000..5f0080c2b76
--- /dev/null
+++ b/app/views/notify/new_ssh_key_email.text.erb
@@ -0,0 +1,7 @@
+Hi <%= @user.name %>!
+
+A new public key was added to your account:
+
+title.................. <%= @key.title %>
+
+If this key was added in error, you can remove it here: <%= profile_keys_url %>
diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml
index e8e9735587e..09518cd3c7f 100644
--- a/app/views/notify/new_user_email.html.haml
+++ b/app/views/notify/new_user_email.html.haml
@@ -1,27 +1,21 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{align: "left", style: "padding: 20px 0 0;"}
- %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
- Hi #{@user['name']}!
- %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
- - if Gitlab.config.gitlab.signup_enabled
- Account has been created successfully.
- - else
- Administrator created account for you. Now you are a member of company GitLab application.
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{style: "padding: 15px 0 15px;", valign: "top"}
- %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 28px; font-size: 16px;font-family: Helvetica, Arial, sans-serif; "}
- login..........................................
- %code= @user['email']
- %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 28px; font-size: 16px;font-family: Helvetica, Arial, sans-serif; "}
- - unless Gitlab.config.gitlab.signup_enabled
- password..................................
- %code= @password
- %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 28px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
- = link_to "Click here to login", root_url
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
+%p
+ Hi #{@user['name']}!
+%p
+ - if Gitlab.config.gitlab.signup_enabled
+ Your account has been created successfully.
+ - else
+ The Administrator created an account for you. Now you are a member of the company GitLab application.
+%p
+ login..........................................
+ %code= @user['email']
+- if @user.created_by_id
+ %p
+ password..................................
+ %code= @password
+
+ %p
+ You will be forced to change this password immediately after login.
+
+%p
+ = link_to "Click here to login", root_url
diff --git a/app/views/notify/new_user_email.text.erb b/app/views/notify/new_user_email.text.erb
new file mode 100644
index 00000000000..c21c95d3047
--- /dev/null
+++ b/app/views/notify/new_user_email.text.erb
@@ -0,0 +1,13 @@
+Hi <%= @user.name %>!
+
+The Administrator created an account for you. Now you are a member of the company GitLab application.
+
+login.................. <%= @user.email %>
+<% if @user.created_by_id %>
+ password............... <%= @password %>
+
+ You will be forced to change this password immediately after login.
+<% end %>
+
+
+Click here to login: <%= url_for(root_url) %>
diff --git a/app/views/notify/note_commit_email.html.haml b/app/views/notify/note_commit_email.html.haml
index e87f9c120e4..620b258fc15 100644
--- a/app/views/notify/note_commit_email.html.haml
+++ b/app/views/notify/note_commit_email.html.haml
@@ -1,23 +1,5 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{align: "left", style: "padding: 20px 0 0;"}
- %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
- = "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}")
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{style: "padding: 15px 0 15px;", valign: "top"}
- %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
- %a{href: "#", style: "color: #0eb6ce; text-decoration: none;"} #{@note.author_name}
- left next message:
- %br
- %table{border: "0", cellpadding: "0", cellspacing: "0", width: "558"}
- %tr
- %td{valign: "top"}
- %div{ style: "background:#f5f5f5; padding:20px;border:1px solid #ddd" }
- = markdown(@note.note)
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
+%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_commit_email.text.erb b/app/views/notify/note_commit_email.text.erb
new file mode 100644
index 00000000000..aab8e5cfb6c
--- /dev/null
+++ b/app/views/notify/note_commit_email.text.erb
@@ -0,0 +1,9 @@
+New comment for Commit <%= @commit.short_id %>
+
+<%= url_for(project_commit_url(@note.project, id: @commit.id, anchor: "note_#{@note.id}")) %>
+
+
+Author: <%= @note.author_name %>
+
+<%= @note.note %>
+
diff --git a/app/views/notify/note_issue_email.html.haml b/app/views/notify/note_issue_email.html.haml
index 832f5df4463..b3230953e7d 100644
--- a/app/views/notify/note_issue_email.html.haml
+++ b/app/views/notify/note_issue_email.html.haml
@@ -1,23 +1,4 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{align: "left", style: "padding: 20px 0 0;"}
- %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
- = "New comment for Issue ##{@issue.id}"
- = link_to_gfm truncate(@issue.title, length: 35), project_issue_url(@issue.project, @issue, anchor: "note_#{@note.id}")
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{style: "padding: 15px 0 15px;", valign: "top"}
- %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
- %a{href: "#", style: "color: #0eb6ce; text-decoration: none;"} #{@note.author_name}
- left next message:
- %br
- %table{border: "0", cellpadding: "0", cellspacing: "0", width: "558"}
- %tr
- %td{valign: "top"}
- %div{ style: "background:#f5f5f5; padding:20px;border:1px solid #ddd" }
- = markdown(@note.note)
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
-
+%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_issue_email.text.erb b/app/views/notify/note_issue_email.text.erb
new file mode 100644
index 00000000000..8a61f54a337
--- /dev/null
+++ b/app/views/notify/note_issue_email.text.erb
@@ -0,0 +1,9 @@
+New comment for Issue <%= @issue.iid %>
+
+<%= url_for(project_issue_url(@issue.project, @issue, anchor: "note_#{@note.id}")) %>
+
+
+Author: <%= @note.author_name %>
+
+<%= @note.note %>
+
diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml
index 3857f2f0318..d587b068486 100644
--- a/app/views/notify/note_merge_request_email.html.haml
+++ b/app/views/notify/note_merge_request_email.html.haml
@@ -1,27 +1,8 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{width: "21"}
- %td
- %h2{style: "color:#646464; font-weight: normal;"}
- - if @note.for_diff_line?
- = link_to "New comment on diff", diffs_project_merge_request_url(@merge_request.project, @merge_request, anchor: "note_#{@note.id}")
- - else
- = link_to "New comment", project_merge_request_url(@merge_request.project, @merge_request, anchor: "note_#{@note.id}")
- for Merge Request ##{@merge_request.id}
- %cite "#{truncate(@merge_request.title, length: 20)}"
- %td{width: "21"}
- %tr
- %td{width: "21"}
- %td
- %p
- %strong #{@note.author_name}
- left next message:
- %br
- %table{border: "0", cellpadding: "0", cellspacing: "0", width: "558"}
- %tr
- %td{valign: "top"}
- %div{ style: "background:#f5f5f5; padding:10px 20px;border:1px solid #ddd" }
- = markdown(@note.note)
- %td{width: "21"}
-
+%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)}"
+= render 'note_message'
diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb
new file mode 100644
index 00000000000..79e72ca16c6
--- /dev/null
+++ b/app/views/notify/note_merge_request_email.text.erb
@@ -0,0 +1,9 @@
+New comment for Merge Request <%= @merge_request.iid %>
+
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %>
+
+
+<%= @note.author_name %>
+
+<%= @note.note %>
+
diff --git a/app/views/notify/note_wall_email.html.haml b/app/views/notify/note_wall_email.html.haml
index 0661c5801b8..92200e83efa 100644
--- a/app/views/notify/note_wall_email.html.haml
+++ b/app/views/notify/note_wall_email.html.haml
@@ -1,22 +1,5 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{align: "left", style: "padding: 20px 0 0;"}
- %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
- New message on
- = link_to "Project Wall", wall_project_url(@note.project, anchor: "note_#{@note.id}")
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{style: "padding: 15px 0 15px;", valign: "top"}
- %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
- %a{href: "#", style: "color: #0eb6ce; text-decoration: none;"} #{@note.author_name}
- left next message:
- %br
- %table{border: "0", cellpadding: "0", cellspacing: "0", width: "558"}
- %tr
- %td{valign: "top"}
- %div{ style: "background:#f5f5f5; padding:20px;border:1px solid #ddd" }
- = markdown(@note.note)
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
+%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/note_wall_email.text.erb b/app/views/notify/note_wall_email.text.erb
new file mode 100644
index 00000000000..ee0dc3db1db
--- /dev/null
+++ b/app/views/notify/note_wall_email.text.erb
@@ -0,0 +1,9 @@
+New message on the project wall <%= @note.project %>
+
+<%= url_for(project_wall_url(@note.project, anchor: "note_#{@note.id}")) %>
+
+
+<%= @note.author_name %>
+
+<%= @note.note %>
+
diff --git a/app/views/notify/project_access_granted_email.html.haml b/app/views/notify/project_access_granted_email.html.haml
index 11117bf0b33..ce34f825358 100644
--- a/app/views/notify/project_access_granted_email.html.haml
+++ b/app/views/notify/project_access_granted_email.html.haml
@@ -1,15 +1,5 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{width: "21"}
- %td
- %h2{style: "color:#646464;" }
- = "You have been granted #{@users_project.project_access_human} access to project"
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %tr
- %td{width: "21"}
- %td
- %h3
- = link_to project_url(@project) do
- = @project.name_with_namespace
- %br
+%p
+ = "You have been granted #{@users_project.human_access} access to project"
+%p
+ = link_to project_url(@project) do
+ = @project.name_with_namespace
diff --git a/app/views/notify/project_access_granted_email.text.erb b/app/views/notify/project_access_granted_email.text.erb
new file mode 100644
index 00000000000..66c57def375
--- /dev/null
+++ b/app/views/notify/project_access_granted_email.text.erb
@@ -0,0 +1,4 @@
+
+You have been granted <%= @users_project.human_access %> access to project <%= @project.name_with_namespace %>
+
+<%= url_for(project_url(@project)) %>
diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml
index 222bd0fecea..3e761c43435 100644
--- a/app/views/notify/project_was_moved_email.html.haml
+++ b/app/views/notify/project_was_moved_email.html.haml
@@ -1,25 +1,11 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #555; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{width: "21"}
- %td
- %h2
- = "Project was moved to another location"
- %td{width: "21"}
- %tr
- %td{width: "21"}
- %td
- %p
- The project is now located under
- = link_to project_url(@project) do
- = @project.name_with_namespace
- %p
- To update the remote url in your local repository run:
- %br
- %table{border: "0", cellpadding: "0", cellspacing: "0", width: "558"}
- %tr
- %td{valign: "top"}
- %p{ style: "background:#f5f5f5; padding:10px; border:1px solid #ddd" }
- git remote set-url origin #{@project.ssh_url_to_repo}
- %br
- %td{ width: "21"}
+%p
+ = "Project was moved to another location"
+%p
+ The project is now located under
+ = link_to project_url(@project) do
+ = @project.name_with_namespace
+%p
+ To update the remote url in your local repository run:
+%p{ style: "background:#f5f5f5; padding:10px; border:1px solid #ddd" }
+ git remote set-url origin #{@project.ssh_url_to_repo}
+%br
diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb
new file mode 100644
index 00000000000..7889c7b9cc4
--- /dev/null
+++ b/app/views/notify/project_was_moved_email.text.erb
@@ -0,0 +1,8 @@
+Project was moved to another location
+
+The project is now located under
+<%= project_url(@project) %>
+
+
+To update the remote url in your local repository run:
+ git remote set-url origin <%= @project.ssh_url_to_repo %>
diff --git a/app/views/notify/reassigned_issue_email.html.haml b/app/views/notify/reassigned_issue_email.html.haml
index bc2d6f7078b..b0edff44ce6 100644
--- a/app/views/notify/reassigned_issue_email.html.haml
+++ b/app/views/notify/reassigned_issue_email.html.haml
@@ -1,16 +1,11 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td
- %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
- = "Reassigned Issue ##{@issue.id}"
- = link_to_gfm truncate(@issue.title, length: 30), project_issue_url(@issue.project, @issue)
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{style: "padding: 15px 0 15px;", valign: "top"}
- %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
- Assignee changed from #{@previous_assignee.name} to #{@issue.assignee_name}
- %td
+%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
+ %strong #{@previous_assignee.name}
+ to
+ %strong #{@issue.assignee_name}
diff --git a/app/views/notify/reassigned_issue_email.text.erb b/app/views/notify/reassigned_issue_email.text.erb
new file mode 100644
index 00000000000..bc0d0567922
--- /dev/null
+++ b/app/views/notify/reassigned_issue_email.text.erb
@@ -0,0 +1,5 @@
+Reassigned Issue <%= @issue.iid %>
+
+<%= url_for(project_issue_url(@issue.project, @issue)) %>
+
+Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= @issue.assignee_name %>
diff --git a/app/views/notify/reassigned_merge_request_email.html.haml b/app/views/notify/reassigned_merge_request_email.html.haml
index 8f7308b3dba..3a615f86e69 100644
--- a/app/views/notify/reassigned_merge_request_email.html.haml
+++ b/app/views/notify/reassigned_merge_request_email.html.haml
@@ -1,16 +1,10 @@
-%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
- %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{align: "left", style: "padding: 20px 0 0;"}
- %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
- = "Reassigned Merge Request !#{@merge_request.id}"
- = link_to_gfm truncate(@merge_request.title, length: 30), project_merge_request_url(@merge_request.project, @merge_request)
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %tr
- %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
- %td{style: "padding: 15px 0 15px;", valign: "top"}
- %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
- Assignee changed from #{@previous_assignee.name} to #{@merge_request.assignee_name}
- %td
-
+%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
+ %strong #{@previous_assignee.name}
+ to
+ %strong #{@merge_request.assignee_name}
diff --git a/app/views/notify/reassigned_merge_request_email.text.erb b/app/views/notify/reassigned_merge_request_email.text.erb
new file mode 100644
index 00000000000..eecf055ff6f
--- /dev/null
+++ b/app/views/notify/reassigned_merge_request_email.text.erb
@@ -0,0 +1,7 @@
+Reassigned Merge Request <%= @merge_request.iid %>
+
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
+
+
+Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= @merge_request.assignee_name %>
+
diff --git a/app/views/profiles/account.html.haml b/app/views/profiles/account.html.haml
index 5465d1f96e9..42c7ec051cb 100644
--- a/app/views/profiles/account.html.haml
+++ b/app/views/profiles/account.html.haml
@@ -1,86 +1,141 @@
-- if Gitlab.config.omniauth.enabled
- %fieldset
- %legend Social Accounts
- .oauth_select_holder
- %p.hint Tip: Click on icon to activate sigin with one of the following services
- - User.omniauth_providers.each do |provider|
- %span{class: oauth_active_class(provider) }
- = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider)
+%h3.page-title
+ Account settings
+%p.light
+ You can change your password, username and private token here.
+ - if current_user.ldap_user?
+ Some options are unavailable for LDAP accounts
+%hr
+.row
+ .span2
+ %ul.nav.nav-pills.nav-stacked.nav-stacked-menu
+ %li.active
+ = link_to '#tab-token', 'data-toggle' => 'tab' do
+ Private Token
+ %li
+ = link_to '#tab-password', 'data-toggle' => 'tab' do
+ Password
-%fieldset
- %legend
- Private token
- %span.cred.pull-right
- keep it secret!
- .padded
- = form_for @user, url: reset_private_token_profile_path, method: :put do |f|
- .data
- %p.slead
- Private token used to access application resources without authentication.
- %br
- It can be used for atom feed or API
- %p.cgray
- - if current_user.private_token
- = text_field_tag "token", current_user.private_token, class: "xxlarge large_text"
- = f.submit 'Reset', confirm: "Are you sure?", class: "btn btn-primary btn-build-token"
- - else
- %span You don`t have one yet. Click generate to fix it.
- = f.submit 'Generate', class: "btn success btn-build-token"
+ - if show_profile_social_tab?
+ %li
+ = link_to '#tab-social', 'data-toggle' => 'tab' do
+ Social Accounts
-%fieldset
- %legend Password
- = form_for @user, url: update_password_profile_path, method: :put do |f|
- .padded
- %p.slead After successful password update you will be redirected to login page where you should login with new password
- -if @user.errors.any?
- .alert.alert-error
- %ul
- - @user.errors.full_messages.each do |msg|
- %li= msg
+ - if show_profile_username_tab?
+ %li
+ = link_to '#tab-username', 'data-toggle' => 'tab' do
+ Change Username
- .clearfix
- = f.label :password
- .input= f.password_field :password, required: true
- .clearfix
- = f.label :password_confirmation
- .input
- = f.password_field :password_confirmation, required: true
- .clearfix
- .input
- = f.submit 'Save password', class: "btn btn-save"
+ - if show_profile_remove_tab?
+ %li
+ = link_to '#tab-remove', 'data-toggle' => 'tab' do
+ Remove Account
+ .span10
+ .tab-content
+ .tab-pane.active#tab-token
+ %fieldset.update-token
+ %legend
+ Private token
+ %span.cred.pull-right
+ keep it secret!
+ %div
+ = form_for @user, url: reset_private_token_profile_path, method: :put do |f|
+ .data
+ %p.slead
+ Your private token is used to access application resources without authentication.
+ %br
+ It can be used for atom feeds or the API.
+ %p.cgray
+ - if current_user.private_token
+ = text_field_tag "token", current_user.private_token, class: "input-xxlarge large_text input-xpadding"
+ = f.submit 'Reset', confirm: "Are you sure?", class: "btn btn-primary btn-build-token"
+ - else
+ %span You don`t have one yet. Click generate to fix it.
+ = f.submit 'Generate', class: "btn success btn-build-token"
+ .tab-pane#tab-password
+ %fieldset.update-password
+ %legend Password
+ - if current_user.ldap_user?
+ %h3.nothing_here_message Not available for LDAP user
+ - else
+ = form_for @user, url: update_password_profile_path, method: :put do |f|
+ %div
+ %p.slead
+ You must provide current password in order to change it.
+ %br
+ After a successful password update you will be redirected to login page where you should login with your new password
+ -if @user.errors.any?
+ .alert.alert-error
+ %ul
+ - @user.errors.full_messages.each do |msg|
+ %li= msg
+ .control-group
+ = f.label :current_password, class: 'cgreen'
+ .controls= f.password_field :current_password, required: true
+ .control-group
+ = f.label :password, 'New password'
+ .controls= f.password_field :password, required: true
+ .control-group
+ = f.label :password_confirmation
+ .controls
+ = f.password_field :password_confirmation, required: true
+ .control-group
+ .controls
+ = f.submit 'Save password', class: "btn btn-save"
+ - if show_profile_social_tab?
+ .tab-pane#tab-social
+ %fieldset
+ %legend Social Accounts
+ .oauth_select_holder
+ %p.hint Tip: Click on icon to activate signin with one of the following services
+ - enabled_social_providers.each do |provider|
+ %span{class: oauth_active_class(provider) }
+ = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider)
-%fieldset.update-username
- %legend
- Username
- %small.cred.pull-right
- Changing your username can have unintended side effects!
- = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f|
- .padded
- = f.label :username
- .input
- = f.text_field :username, required: true
- &nbsp;
- %span.loading-gif.hide= image_tag "ajax_loader.gif"
- %span.update-success.cgreen.hide
- %i.icon-ok
- Saved
- %span.update-failed.cred.hide
- %i.icon-remove
- Failed
- %ul.cred
- %li It will change web url for personal projects.
- %li It will change the git path to repositories for personal projects.
- .input
- = f.submit 'Save username', class: "btn btn-save"
+ - if show_profile_username_tab?
+ .tab-pane#tab-username
+ %fieldset.update-username
+ %legend
+ Username
+ %small.cred.pull-right
+ Changing your username can have unintended side effects!
+ = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f|
+ %div
+ .control-group
+ = f.label :username
+ .controls
+ = f.text_field :username, required: true
+ &nbsp;
+ %span.loading-gif.hide= image_tag "ajax_loader.gif"
+ %span.update-success.cgreen.hide
+ %i.icon-ok
+ Saved
+ %span.update-failed.cred.hide
+ %i.icon-remove
+ Failed
+ %ul.cred
+ %li This will change the web URL for personal projects.
+ %li This will change the git path to repositories for personal projects.
+ .controls
+ = f.submit 'Save username', class: "btn btn-save"
-- if Gitlab.config.gitlab.signup_enabled
- %fieldset.remove-account
- %legend
- Remove account
- %small.cred.pull-right
- Before removing the account you must remove all projects!
- = link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove delete-key btn-small pull-right" \ No newline at end of file
+ - if show_profile_remove_tab?
+ .tab-pane#tab-remove
+ %fieldset.remove-account
+ %legend
+ Remove account
+ %div
+ %p Deleting an account has the following effects:
+ %ul
+ %li All user content like authored issues, snippets, comments will be removed
+ - rp = current_user.personal_projects.count
+ - unless rp.zero?
+ %li #{pluralize rp, 'personal project'} will be removed and cannot be restored
+ - if current_user.solo_owned_groups.present?
+ %li
+ Next groups will be abandoned. You should transfer or remove them:
+ %strong #{current_user.solo_owned_groups.map(&:name).join(', ')}
+ = link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove"
diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml
index f4b50677203..0d8075b7d43 100644
--- a/app/views/profiles/design.html.haml
+++ b/app/views/profiles/design.html.haml
@@ -1,10 +1,13 @@
+%h3.page-title
+ My appearance settings
+%p.light
+ Appearance settings saved to your profile and available across all devices
+%hr
+
= form_for @user, url: profile_path, remote: true, method: :put do |f|
%fieldset.application-theme
%legend
Application theme
- .update-feedback.hide
- %i.icon-ok
- Saved
.themes_opts
= label_tag do
.prev.default
@@ -24,7 +27,7 @@
= label_tag do
.prev.gray
= f.radio_button :theme_id, 4
- SlateGray
+ Gray
= label_tag do
.prev.violet
@@ -36,17 +39,10 @@
%fieldset.code-preview-theme
%legend
Code preview theme
- .update-feedback.hide
- %i.icon-ok
- Saved
.code_highlight_opts
- = label_tag do
- .prev
- = image_tag "white.png"
- = f.radio_button :dark_scheme, false
- White code preview
- = label_tag do
- .prev
- = image_tag "dark.png"
- = f.radio_button :dark_scheme, true
- Dark code preview
+ - color_schemes.each do |color_scheme_id, color_scheme|
+ = label_tag do
+ .prev
+ = image_tag "#{color_scheme}-scheme-preview.png"
+ = f.radio_button :color_scheme_id, color_scheme_id
+ = color_scheme.gsub(/[-_]+/, ' ').humanize
diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml
new file mode 100644
index 00000000000..6fc27be5db4
--- /dev/null
+++ b/app/views/profiles/groups/index.html.haml
@@ -0,0 +1,38 @@
+%h3.page-title
+ Group membership
+ - if current_user.can_create_group?
+ %span.pull-right
+ = link_to new_group_path, class: "btn btn-new" do
+ %i.icon-plus
+ New Group
+%p.light
+ Group members have access to all a group's projects
+%hr
+.ui-box
+ .title
+ %strong Groups
+ (#{@user_groups.count})
+ %ul.well-list
+ - @user_groups.each do |user_group|
+ - group = user_group.group
+ %li
+ .pull-right
+ - if can?(current_user, :manage_group, group)
+ = link_to edit_group_path(group), class: "btn-small btn grouped" do
+ %i.icon-cogs
+ Settings
+
+ = link_to leave_profile_group_path(group), confirm: "Are you sure you want to leave #{group.name} group?", method: :delete, class: "btn-small btn grouped", title: 'Remove user from group' do
+ %i.icon-signout
+ Leave
+
+ = link_to group, class: 'group-name' do
+ %strong= group.name
+
+ as
+ %strong #{user_group.human_access}
+
+ %div.light
+ #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
+
+= paginate @user_groups
diff --git a/app/views/profiles/history.html.haml b/app/views/profiles/history.html.haml
index aa7006c569b..3951c47b5f2 100644
--- a/app/views/profiles/history.html.haml
+++ b/app/views/profiles/history.html.haml
@@ -1,3 +1,8 @@
+%h3.page-title
+ Account history
+%p.light
+ All events created by your account are listed here
+%hr
.profile_history
= render @events
%hr
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
new file mode 100644
index 00000000000..158979c0ee5
--- /dev/null
+++ b/app/views/profiles/keys/_form.html.haml
@@ -0,0 +1,23 @@
+%div
+ = form_for [:profile, @key] do |f|
+ - if @key.errors.any?
+ .alert.alert-error
+ %ul
+ - @key.errors.full_messages.each do |msg|
+ %li= msg
+
+ .control-group
+ = f.label :title
+ .controls= f.text_field :title, class: "input-xlarge"
+ .control-group
+ = f.label :key
+ .controls
+ %p.light
+ Paste your public key here. Read more about how to generate a key on #{link_to "the SSH help page", help_ssh_path}.
+ = f.text_area :key, class: "input-xxlarge thin_area"
+
+
+ .form-actions
+ = f.submit 'Add key', class: "btn btn-create"
+ = link_to "Cancel", profile_keys_path, class: "btn btn-cancel"
+
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
new file mode 100644
index 00000000000..d0a3fe32c35
--- /dev/null
+++ b/app/views/profiles/keys/_key.html.haml
@@ -0,0 +1,11 @@
+%li
+ = link_to profile_key_path(key) do
+ %strong= key.title
+ %span
+ (#{key.fingerprint})
+ %span.cgray
+ added
+ = time_ago_in_words(key.created_at)
+ ago
+
+ = link_to 'Remove', profile_key_path(key), confirm: 'Are you sure?', method: :delete, class: "btn btn-small btn-remove delete-key pull-right"
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
new file mode 100644
index 00000000000..79fdb164089
--- /dev/null
+++ b/app/views/profiles/keys/index.html.haml
@@ -0,0 +1,22 @@
+%h3.page-title
+ My SSH keys
+ .pull-right
+ = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
+%p.light
+ SSH keys allow you to establish a secure connection between your computer and GitLab
+ %br
+ Before you can add an SSH key you need to
+ = link_to "generate it", help_ssh_path
+%hr
+
+
+.ui-box
+ .title
+ SSH Keys (#{@keys.count})
+ %ul.well-list#keys-table
+ = render @keys
+ - if @keys.blank?
+ %li
+ %h3.nothing_here_message There are no SSH keys with access to your account.
+
+
diff --git a/app/views/keys/new.html.haml b/app/views/profiles/keys/new.html.haml
index fff3805890e..f1b8fe08d5c 100644
--- a/app/views/keys/new.html.haml
+++ b/app/views/profiles/keys/new.html.haml
@@ -1,4 +1,4 @@
-%h3.page_title Add an SSH Key
+%h3.page-title Add an SSH Key
%hr
= render 'form'
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
new file mode 100644
index 00000000000..b736ab17087
--- /dev/null
+++ b/app/views/profiles/keys/show.html.haml
@@ -0,0 +1,22 @@
+.row
+ .span4
+ .ui-box
+ .title
+ SSH Key
+ %ul.well-list
+ %li
+ %span.light Title:
+ %strong= @key.title
+ %li
+ %span.light Created at:
+ %strong= @key.created_at.stamp("Aug 21, 2011")
+
+ .span8
+ %p
+ %span.light Fingerprint:
+ %strong= @key.fingerprint
+ %pre.well-pre
+ = @key.key
+
+.pull-right
+ = link_to 'Remove', profile_key_path(@key), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove delete-key"
diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml
new file mode 100644
index 00000000000..5f62c8099d0
--- /dev/null
+++ b/app/views/profiles/notifications/_settings.html.haml
@@ -0,0 +1,31 @@
+%li
+ .row
+ .span4
+ %span
+ = notification_icon(notification)
+
+ - if membership.kind_of? UsersGroup
+ = link_to membership.group.name, membership.group
+ - else
+ = link_to_project(membership.project)
+ .span7
+ = 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 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 do
+ = radio_button_tag :notification_level, Notification::N_DISABLED, notification.disabled?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit'
+ %span Disabled
+
+ = label_tag do
+ = radio_button_tag :notification_level, Notification::N_PARTICIPATING, notification.participating?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit'
+ %span Participating
+
+ = label_tag do
+ = radio_button_tag :notification_level, Notification::N_WATCH, notification.watch?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit'
+ %span Watch
+
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
new file mode 100644
index 00000000000..8353b2f5f23
--- /dev/null
+++ b/app/views/profiles/notifications/show.html.haml
@@ -0,0 +1,58 @@
+%h3.page-title
+ Notifications settings
+%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
+
+.row
+ .span4
+ %h4
+ = notification_icon(@notification)
+ Global setting
+ .span7
+ = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
+ = hidden_field_tag :notification_type, 'global'
+
+ = label_tag do
+ = radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit'
+ %span Disabled
+
+ = label_tag do
+ = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit'
+ %span Participating
+
+ = label_tag do
+ = radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit'
+ %span Watch
+
+%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
+ %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
+
+ %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/notifications/update.js.haml b/app/views/profiles/notifications/update.js.haml
new file mode 100644
index 00000000000..84c6ab25599
--- /dev/null
+++ b/app/views/profiles/notifications/update.js.haml
@@ -0,0 +1,6 @@
+- if @saved
+ :plain
+ new Flash("Notification settings saved", "notice")
+- else
+ :plain
+ new Flash("Failed to save new settings", "alert")
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
new file mode 100644
index 00000000000..a4e7dadd16a
--- /dev/null
+++ b/app/views/profiles/passwords/new.html.haml
@@ -0,0 +1,22 @@
+= form_for @user, url: profile_password_path, method: :post do |f|
+ .light-well.padded
+ %p.slead
+ Please set new password before proceed.
+ %br
+ After successful password update you will be redirected to login screen
+ -if @user.errors.any?
+ .alert.alert-error
+ %ul
+ - @user.errors.full_messages.each do |msg|
+ %li= msg
+
+ .control-group
+ = f.label :password
+ .controls= f.password_field :password, required: true
+ .control-group
+ = f.label :password_confirmation
+ .controls
+ = f.password_field :password_confirmation, required: true
+ .control-group
+ .controls
+ = f.submit 'Set new password', class: "btn btn-create"
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 3cf6330cc3c..25bf7912f1e 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,6 +1,5 @@
-.profile_avatar_holder
- = image_tag gravatar_icon(@user.email, 90)
-%h3.page_title
+= image_tag gravatar_icon(@user.email, 60), alt: '', class: 'avatar s60'
+%h3.page-title
= @user.name
%br
%small
@@ -51,7 +50,7 @@
%legend Tips:
%ul
%li
- %p You can change your password on Account page
+ %p You can change your password on the Account page
- if Gitlab.config.gravatar.enabled
%li
%p You can change your avatar at #{link_to "gravatar.com", "http://gravatar.com"}
@@ -67,30 +66,25 @@
Need a group for several dependent projects?
= link_to new_group_path, class: "btn btn-tiny" do
Create a group
- - if current_user.can_create_team?
- %li
- %p
- Want to share a team between projects?
- = link_to new_team_path, class: "btn btn-tiny" do
- Create a team
- %fieldset
- %legend
- Personal projects:
- %small.pull-right
- %span= current_user.personal_projects.count
- of
- %span= current_user.projects_limit
- .padded
- .progress
- .bar{style: "width: #{current_user.projects_limit_percent}%;"}
+ - unless current_user.projects_limit_left > 100
+ %fieldset
+ %legend
+ Personal projects:
+ %small.pull-right
+ %span= current_user.personal_projects.count
+ of
+ %span= current_user.projects_limit
+ .padded
+ .progress
+ .bar{style: "width: #{current_user.projects_limit_percent}%;"}
%fieldset
%legend
SSH public keys:
%span.pull-right
- = link_to pluralize(current_user.keys.count, 'key'), keys_path
+ = link_to pluralize(current_user.keys.count, 'key'), profile_keys_path
.padded
- = link_to "Add Public Key", new_key_path, class: "btn btn-small"
+ = link_to "Add Public Key", new_profile_key_path, class: "btn btn-small"
.form-actions
- = f.submit 'Save', class: "btn btn-save"
+ = f.submit 'Save changes', class: "btn btn-save"
diff --git a/app/views/projects/_clone_panel.html.haml b/app/views/projects/_clone_panel.html.haml
index e52df19be96..c2f85e8ebe8 100644
--- a/app/views/projects/_clone_panel.html.haml
+++ b/app/views/projects/_clone_panel.html.haml
@@ -1,17 +1,56 @@
.project_clone_panel
.row
- .span7
+ .span8
.form-horizontal= render "shared/clone_panel"
- .span4.pull-right
+ .span3.pull-right
.pull-right
- unless @project.empty_repo?
+ - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
+ - if current_user.already_forked?(@project)
+ = link_to project_path(current_user.fork_of(@project)), class: 'btn grouped disabled' do
+ %i.icon-code-fork
+ Forked
+ - else
+ = link_to fork_project_path(@project), title: "Fork", class: "btn grouped", method: "POST" do
+ %i.icon-code-fork
+ Fork
- if can? current_user, :download_code, @project
- = link_to archive_project_repository_path(@project), class: "btn-small btn grouped" do
+ = link_to archive_project_repository_path(@project), class: "btn grouped" do
%i.icon-download-alt
- Download
- - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
- = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn-small btn grouped" do
- Merge Request
- - if @project.issues_enabled && can?(current_user, :write_issue, @project)
- = link_to new_project_issue_path(@project), title: "New Issue", class: "btn-small btn grouped" do
- Issue
+ %span.only-wide Download
+
+ - if current_user
+ .dropdown.pull-right
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.icon-plus-sign-alt
+ %span.only-wide New
+ %b.caret
+ %ul.dropdown-menu
+ - if @project.issues_enabled && can?(current_user, :write_issue, @project)
+ %li
+ = link_to url_for_new_issue, title: "New Issue" do
+ Issue
+ - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
+ %li
+ = link_to new_project_merge_request_path(@project), title: "New Merge Request" do
+ Merge Request
+ - if @project.snippets_enabled && can?(current_user, :write_snippet, @project)
+ %li
+ = link_to new_project_snippet_path(@project), title: "New Snippet" do
+ Snippet
+ - if can? current_user, :push_code, @project
+ %li.divider
+ %li
+ = link_to new_project_branch_path(@project) do
+ %i.icon-code-fork
+ Git branch
+ %li
+ = link_to new_project_tag_path(@project) do
+ %i.icon-tag
+ Git tag
+
+ - if can?(current_user, :admin_team_member, @project)
+ %li.divider
+ %li
+ = link_to new_project_team_member_path(@project), title: "New project member" do
+ Project member
diff --git a/app/views/projects/_errors.html.haml b/app/views/projects/_errors.html.haml
new file mode 100644
index 00000000000..bb9759353a3
--- /dev/null
+++ b/app/views/projects/_errors.html.haml
@@ -0,0 +1,4 @@
+- if @project.errors.any?
+ .alert.alert-error
+ %button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
+ = @project.errors.full_messages.first
diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml
deleted file mode 100644
index 0336654dc69..00000000000
--- a/app/views/projects/_form.html.haml
+++ /dev/null
@@ -1,85 +0,0 @@
-= form_for(@project, remote: true) do |f|
- - if @project.errors.any?
- .alert.alert-error
- %ul
- - @project.errors.full_messages.each do |msg|
- %li= msg
- .clearfix.project_name_holder
- = f.label :name do
- Project name is
- .input
- = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
- - unless @repository.heads.empty?
- .clearfix
- = f.label :default_branch, "Default Branch"
- .input= f.select(:default_branch, @repository.heads.map(&:name), {}, style: "width:210px;")
-
-
- %fieldset.features
- %legend Features:
-
- .control-group
- = f.label :issues_enabled, "Issues", class: 'control-label'
- .controls
- = f.check_box :issues_enabled
- %span.descr Lightweight issue tracking system for this project
-
- .control-group
- = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label'
- .controls
- = f.check_box :merge_requests_enabled
- %span.descr Submit changes to be merged upstream.
-
- .control-group
- = f.label :wall_enabled, "Wall", class: 'control-label'
- .controls
- = f.check_box :wall_enabled
- %span.descr Simple chat system for broadcasting inside project
-
- .control-group
- = f.label :wiki_enabled, "Wiki", class: 'control-label'
- .controls
- = f.check_box :wiki_enabled
- %span.descr Pages for project documentation
-
- - if can?(current_user, :change_public_mode, @project)
- %fieldset.features
- %legend
- %i.icon-share
- Public mode:
- .control-group
- = f.label :public, class: 'control-label' do
- %span Public clone access
- .controls
- = f.check_box :public
- %span.descr
- If checked, this project can be cloned
- %em without any
- authentification.
- It will also be listed on the #{link_to "public access directory", public_root_path}.
-
-
- - if can? current_user, :change_namespace, @project
- %fieldset.features
- %legend Transfer:
- .control-group
- = f.label :namespace_id do
- %span Namespace
- .controls
- = f.select :namespace_id, namespaces_options(@project.namespace_id || Namespace::global_id), {prompt: 'Choose a project namespace'}, {class: 'chosen'}
- %br
- %ul.prepend-top-10.cred
- %li Be careful. Changing project namespace can have unintended side effects
- %li You can transfer project only to namespaces you can manage
- %li You will need to update your local repositories to point to the new location.
-
-
- %br
-
- .actions
- = f.submit 'Save', class: "btn btn-save"
- = link_to 'Cancel', @project, class: "btn"
- - unless @project.new_record?
- - if can?(current_user, :remove_project, @project)
- .pull-right
- = link_to 'Remove Project', @project, confirm: 'Removed project can not be restored! Are you sure?', method: :delete, class: "btn btn-remove"
diff --git a/app/views/projects/_new_form.html.haml b/app/views/projects/_new_form.html.haml
deleted file mode 100644
index ba3ccc421cd..00000000000
--- a/app/views/projects/_new_form.html.haml
+++ /dev/null
@@ -1,48 +0,0 @@
-= form_for(@project, remote: true) do |f|
- - if @project.errors.any?
- .alert.alert-error
- %span= @project.errors.full_messages.first
- .clearfix.project_name_holder
- = f.label :name do
- Project name is
- .input
- = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
- = f.submit 'Create project', class: "btn btn-create project-submit"
-
- - if current_user.can_select_namespace?
- .clearfix
- = f.label :namespace_id do
- %span Namespace
- .input
- = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen'}
-
-
- .clearfix
- .input
- = link_to "#", class: 'appear-link' do
- %i.icon-upload-alt
- %span Import existing repository?
- .clearfix.appear-data
- = f.label :import_url do
- %span Import existing repo
- .input
- = f.text_field :import_url, class: 'xlarge', placeholder: 'https://github.com/randx/six.git'
- .light
- URL should be clonable
-
- %p.padded
- New projects are private by default. You choose who can see the project and commit to repository.
- %hr
-
- - if current_user.can_create_group?
- .clearfix
- .input.light
- Need a group for several dependent projects?
- = link_to new_group_path, class: "btn btn-tiny" do
- Create a group
- - if current_user.can_create_team?
- .clearfix
- .input.light
- Want to share a project between team?
- = link_to new_team_path, class: "btn btn-tiny" do
- Create a team
diff --git a/app/views/projects/_project_head.html.haml b/app/views/projects/_project_head.html.haml
deleted file mode 100644
index b8c88853a62..00000000000
--- a/app/views/projects/_project_head.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-%ul.nav.nav-tabs
- = nav_link(path: 'projects#show') do
- = link_to project_path(@project), class: "activities-tab tab" do
- %i.icon-home
- Show
- = nav_link(controller: [:team_members, :teams]) do
- = link_to project_team_index_path(@project), class: "team-tab tab" do
- %i.icon-user
- Team
- = nav_link(path: 'projects#files') do
- = link_to 'Attachments', files_project_path(@project), class: "files-tab tab"
- = nav_link(controller: :snippets) do
- = link_to 'Snippets', project_snippets_path(@project), class: "snippets-tab tab"
-
- - if can? current_user, :admin_project, @project
- = nav_link(controller: :deploy_keys, html_options: {class: 'pull-right'}) do
- = link_to project_deploy_keys_path(@project) do
- %span
- Deploy Keys
- = nav_link(controller: :hooks, html_options: {class: 'pull-right'}) do
- = link_to project_hooks_path(@project) do
- %span
- Hooks
- = nav_link(controller: :services, html_options: {class: 'pull-right'}) do
- = link_to project_services_path(@project) do
- %span
- Services
- = nav_link(path: 'projects#edit', html_options: {class: 'pull-right'}) do
- = link_to edit_project_path(@project), class: "stat-tab tab " do
- %i.icon-edit
- Edit
diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml
new file mode 100644
index 00000000000..f59e2871aa3
--- /dev/null
+++ b/app/views/projects/_settings_nav.html.haml
@@ -0,0 +1,21 @@
+%ul.nav.nav-pills.nav-stacked.nav-stacked-menu
+ = nav_link(path: 'projects#edit') do
+ = link_to edit_project_path(@project), class: "stat-tab tab " do
+ %i.icon-edit
+ Edit Project
+ = nav_link(controller: [:team_members, :teams]) do
+ = link_to project_team_index_path(@project), class: "team-tab tab" do
+ %i.icon-group
+ Members
+ = nav_link(controller: :deploy_keys) do
+ = link_to project_deploy_keys_path(@project) do
+ %span
+ Deploy Keys
+ = nav_link(controller: :hooks) do
+ = link_to project_hooks_path(@project) do
+ %span
+ Web Hooks
+ = nav_link(controller: :services) do
+ = link_to project_services_path(@project) do
+ %span
+ Services
diff --git a/app/views/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 36d81e6af38..cdca8b2e634 100644
--- a/app/views/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -1,34 +1,24 @@
-= render "head"
+%h3.page-title Blame view
#tree-holder.tree-holder
- %ul.breadcrumb
- %li
- %span.arrow
- = link_to project_tree_path(@project, @ref) do
- = @project.name
- - @tree.breadcrumbs(6) do |link|
- \/
- %li= link
- .clear
-
- .file_holder
- .file_title
+ .file-holder
+ .file-title
%i.icon-file
%span.file_name
- = @tree.name
- %small= number_to_human_size @tree.size
- %span.options= render "tree/blob_actions"
- .file_content.blame
+ = @path
+ %small= number_to_human_size @blob.size
+ %span.options= render "projects/blob/actions"
+ .file-content.blame
%table
- current_line = 1
- @blame.each do |commit, lines|
- - commit = CommitDecorator.decorate(Commit.new(commit))
+ - commit = Commit.new(commit)
%tr
%td.blame-commit
%span.commit
= link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
&nbsp;
- = commit.author_link avatar: true, size: 16
+ = commit_author_link(commit, avatar: true, size: 16)
&nbsp;
= link_to_gfm truncate(commit.title, length: 20), project_commit_path(@project, commit.id), class: "row_title"
%td.lines.blame-numbers
@@ -39,8 +29,11 @@
- else
- lines.each do |line|
= current_line
+ \
- current_line += 1
%td.lines
%pre
- - lines.each do |line|
- = line
+ :erb
+ <% lines.each do |line| %>
+ <%= line %>
+ <% end %>
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
new file mode 100644
index 00000000000..5641c528a4f
--- /dev/null
+++ b/app/views/projects/blob/_actions.html.haml
@@ -0,0 +1,12 @@
+.btn-group.tree-btn-group
+ -# only show edit link for text files
+ - if @blob.text?
+ = link_to "edit", project_edit_tree_path(@project, @id), class: "btn btn-small", disabled: !allowed_tree_edit?
+ = link_to "raw", project_raw_path(@project, @id), class: "btn btn-small", target: "_blank"
+ -# only show normal/blame view links for text files
+ - if @blob.text?
+ - if current_page? project_blame_path(@project, @id)
+ = link_to "normal view", project_blob_path(@project, @id), class: "btn btn-small"
+ - else
+ = link_to "blame", project_blame_path(@project, @id), class: "btn btn-small" unless @blob.empty?
+ = link_to "history", project_commits_path(@project, @id), class: "btn btn-small"
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
new file mode 100644
index 00000000000..6524aaeef4c
--- /dev/null
+++ b/app/views/projects/blob/_blob.html.haml
@@ -0,0 +1,32 @@
+%ul.breadcrumb
+ %li
+ %i.icon-angle-right
+ = link_to project_tree_path(@project, @ref) do
+ = @project.path
+ - tree_breadcrumbs(@tree, 6) do |title, path|
+ \/
+ %li
+ - if path
+ - if path.end_with?(@path)
+ = link_to project_blob_path(@project, path) do
+ %span.cblue
+ = truncate(title, length: 40)
+ - else
+ = link_to truncate(title, length: 40), project_tree_path(@project, path)
+ - else
+ = link_to title, '#'
+
+%div#tree-content-holder.tree-content-holder
+ .file-holder
+ .file-title
+ %i.icon-file
+ %span.file_name
+ = blob.name
+ %small= number_to_human_size blob.size
+ %span.options= render "actions"
+ - if blob.text?
+ = render "text", blob: blob
+ - elsif blob.image?
+ = render "image", blob: blob
+ - else
+ = render "download", blob: blob
diff --git a/app/views/tree/blob/_download.html.haml b/app/views/projects/blob/_download.html.haml
index 864c209db76..f3da1a2a219 100644
--- a/app/views/tree/blob/_download.html.haml
+++ b/app/views/projects/blob/_download.html.haml
@@ -1,6 +1,6 @@
-.file_content.blob_file
+.file-content.blob_file
%center
- = link_to project_blob_path(@project, @id) do
+ = link_to project_raw_path(@project, @id) do
%div.padded
%h4
%i.icon-download-alt
diff --git a/app/views/tree/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index 7b23f0c810c..c090f690d1d 100644
--- a/app/views/tree/blob/_image.html.haml
+++ b/app/views/projects/blob/_image.html.haml
@@ -1,2 +1,2 @@
-.file_content.image_file
+.file-content.image_file
%img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
diff --git a/app/views/tree/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index 122e275219d..bed493d6d8c 100644
--- a/app/views/tree/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -1,12 +1,12 @@
- if gitlab_markdown?(blob.name)
- .file_content.wiki
+ .file-content.wiki
= preserve do
= markdown(blob.data)
- elsif markup?(blob.name)
- .file_content.wiki
+ .file-content.wiki
= raw GitHub::Markup.render(blob.name, blob.data)
- else
- .file_content.code
+ .file-content.code
- unless blob.empty?
%div{class: user_color_scheme_class}
= raw blob.colorize(formatter: :gitlab)
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
new file mode 100644
index 00000000000..d96595bc7f0
--- /dev/null
+++ b/app/views/projects/blob/show.html.haml
@@ -0,0 +1,4 @@
+%div.tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'blob', path: @path
+%div#tree-holder.tree-holder
+ = render 'blob', blob: @blob
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
new file mode 100644
index 00000000000..b8392525791
--- /dev/null
+++ b/app/views/projects/branches/_branch.html.haml
@@ -0,0 +1,32 @@
+- commit = Commit.new(Gitlab::Git::Commit.new(branch.commit))
+%li
+ %h4
+ = link_to project_commits_path(@project, branch.name) do
+ %strong= truncate(branch.name, length: 60)
+ - if branch.name == @repository.root_ref
+ %span.label.label-info default
+ - if @project.protected_branch? branch.name
+ %span.label.label-success
+ %i.icon-lock
+ .pull-right
+ - if can?(current_user, :download_code, @project)
+ = link_to archive_project_repository_path(@project, ref: branch.name), class: 'btn grouped btn-small' do
+ %i.icon-download-alt
+ Download
+ = 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
+
+ - 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, confirm: 'Removed branch cannot be restored. Are you sure?', remote: true do
+ %i.icon-trash
+
+ %p
+ = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
+ = commit.short_id
+ = image_tag gravatar_icon(commit.author_email), class: "avatar s16", alt: ''
+ %span.light
+ = gfm escape_once(truncate(commit.title, length: 40))
+ %span
+ = time_ago_in_words(commit.committed_date)
+ ago
diff --git a/app/views/projects/branches/_filter.html.haml b/app/views/projects/branches/_filter.html.haml
new file mode 100644
index 00000000000..7ea11a74a2b
--- /dev/null
+++ b/app/views/projects/branches/_filter.html.haml
@@ -0,0 +1,17 @@
+%ul.nav.nav-pills.nav-stacked
+ = nav_link(path: 'branches#recent') do
+ = link_to 'Recent', recent_project_branches_path(@project)
+ = nav_link(path: 'protected_branches#index') do
+ = link_to project_protected_branches_path(@project) do
+ Protected
+ %i.icon-lock
+ = nav_link(path: 'branches#index') do
+ = link_to 'All branches', project_branches_path(@project)
+
+
+%hr
+- if can? current_user, :push_code, @project
+ = link_to new_project_branch_path(@project), class: 'btn btn-create' do
+ %i.icon-add-sign
+ New branch
+
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
new file mode 100644
index 00000000000..45b9c6c8521
--- /dev/null
+++ b/app/views/projects/branches/index.html.haml
@@ -0,0 +1,10 @@
+= render "projects/commits/head"
+.row
+ .span3
+ = render "filter"
+ .span9
+ - unless @branches.empty?
+ %ul.bordered-list.top-list
+ - @branches.each do |branch|
+ = render "projects/branches/branch", branch: branch
+ = paginate @branches, theme: 'gitlab' \ No newline at end of file
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
new file mode 100644
index 00000000000..37612d3fd7b
--- /dev/null
+++ b/app/views/projects/branches/new.html.haml
@@ -0,0 +1,24 @@
+%h3.page-title
+ %i.icon-code-fork
+ New branch
+= form_tag project_branches_path, method: :post do
+ .control-group
+ = label_tag :branch_name, 'Name for new branch', class: 'control-label'
+ .controls
+ = text_field_tag :branch_name, nil, placeholder: 'feature/dashboard', required: true, tabindex: 1
+ .control-group
+ = label_tag :ref, 'Create from', class: 'control-label'
+ .controls
+ = text_field_tag :ref, nil, placeholder: 'master', required: true, tabindex: 2
+ .light branch name or commit SHA
+ .form-actions
+ = submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3
+ = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
+
+:javascript
+ var availableTags = #{@project.repository.ref_names.to_json};
+
+ $("#ref").autocomplete({
+ source: availableTags,
+ minLength: 1
+ });
diff --git a/app/views/projects/branches/recent.html.haml b/app/views/projects/branches/recent.html.haml
new file mode 100644
index 00000000000..25f416c78f2
--- /dev/null
+++ b/app/views/projects/branches/recent.html.haml
@@ -0,0 +1,8 @@
+= render "projects/commits/head"
+.row
+ .span3
+ = render "filter"
+ .span9
+ %ul.bordered-list.top-list
+ - @branches.each do |branch|
+ = render "projects/branches/branch", branch: branch \ No newline at end of file
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
new file mode 100644
index 00000000000..1f493452064
--- /dev/null
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -0,0 +1,47 @@
+.pull-right
+ %div
+ - if @notes_count > 0
+ %span.btn.disabled.grouped
+ %i.icon-comment
+ = @notes_count
+ .pull-left.btn-group
+ %a.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
+ %span Browse Code »
+ %div
+
+%p
+ %span.light Commit
+ = link_to @commit.id, project_commit_path(@project, @commit)
+.commit-info-row
+ %span.light Authored by
+ %strong
+ = commit_author_link(@commit, avatar: true, size: 24)
+ %time{title: @commit.authored_date.stamp("Aug 21, 2011 9:23pm")}
+ #{time_ago_in_words(@commit.authored_date)} ago
+
+- if @commit.different_committer?
+ .commit-info-row
+ %span.light Committed by
+ %strong
+ = commit_committer_link(@commit, avatar: true, size: 24)
+ %time{title: @commit.committed_date.stamp("Aug 21, 2011 9:23pm")}
+ #{time_ago_in_words(@commit.committed_date)} ago
+
+.commit-info-row
+ %span.cgray= pluralize(@commit.parents.count, "parent")
+ - @commit.parents.each do |parent|
+ = link_to parent.id[0...10], project_commit_path(@project, parent)
+
+.commit-box
+ %h3.commit-title
+ = gfm escape_once(@commit.title)
+ - if @commit.description.present?
+ %pre.commit-description
+ = gfm escape_once(@commit.description)
diff --git a/app/views/commit/huge_commit.html.haml b/app/views/projects/commit/huge_commit.html.haml
index 7f0bcf38037..210d8d9e207 100644
--- a/app/views/commit/huge_commit.html.haml
+++ b/app/views/projects/commit/huge_commit.html.haml
@@ -1,3 +1,3 @@
-= render "commits/commit_box"
+= render "projects/commit/commit_box"
.alert.alert-error
%h4 Commit diffs are too big to be displayed
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
new file mode 100644
index 00000000000..da1b4c10f87
--- /dev/null
+++ b/app/views/projects/commit/show.html.haml
@@ -0,0 +1,3 @@
+= render "commit_box"
+= render "projects/commits/diffs", diffs: @commit.diffs, project: @project
+= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
new file mode 100644
index 00000000000..d70fd96accd
--- /dev/null
+++ b/app/views/projects/commits/_commit.html.haml
@@ -0,0 +1,19 @@
+%li.commit
+ .commit-row-title
+ = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
+ &nbsp;
+ = link_to_gfm truncate(commit.title, length: 70), project_commit_path(project, commit.id), class: "commit-row-message"
+ = link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right"
+ .notes_count
+ - notes = project.notes.for_commit_id(commit.id)
+ - if notes.any?
+ %span.badge.badge-info
+ %i.icon-comment
+ = notes.count
+
+ .commit-row-info
+ = commit_author_link(commit, avatar: true, size: 16)
+ %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") }
+ = time_ago_in_words(commit.committed_date)
+ ago
+ &nbsp;
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
new file mode 100644
index 00000000000..b6404778073
--- /dev/null
+++ b/app/views/projects/commits/_commits.html.haml
@@ -0,0 +1,11 @@
+- @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
+ .row.commits-row
+ .span2
+ %h4
+ %i.icon-calendar
+ %span= day.stamp("28 Aug, 2010")
+ %p= pluralize(commits.count, 'commit')
+ .span10
+ %ul.well-list
+ = render commits, project: @project
+ %hr.lists-separator
diff --git a/app/views/commits/_diff_head.html.haml b/app/views/projects/commits/_diff_head.html.haml
index 5aa542287fe..5aa542287fe 100644
--- a/app/views/commits/_diff_head.html.haml
+++ b/app/views/projects/commits/_diff_head.html.haml
diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml
new file mode 100644
index 00000000000..c51f1b6eff5
--- /dev/null
+++ b/app/views/projects/commits/_diffs.html.haml
@@ -0,0 +1,70 @@
+- @suppress_diff ||= @suppress_diff || @force_suppress_diff
+- if @suppress_diff
+ .alert.alert-block
+ %p
+ %strong Warning! This is a large diff.
+ %p
+ To preserve performance the diff is not shown.
+ - 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"
+ or
+ = 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"
+ or
+ = 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"
+
+%p.commit-stat-summary
+ Showing
+ %strong.cdark #{pluralize(diffs.count, "changed file")}
+ - if current_controller?(:commit)
+ - unless @commit.has_zero_stats?
+ with
+ %strong.cgreen #{@commit.stats.additions} additions
+ and
+ %strong.cred #{@commit.stats.deletions} deletions
+.file-stats
+ = render "projects/commits/diff_head", diffs: diffs
+
+.files
+ - unless @suppress_diff
+ - diffs.each_with_index do |diff, i|
+ - next if diff.diff.empty?
+ - file = Gitlab::Git::Blob.new(project.repository, @commit.id, @ref, diff.new_path)
+ - file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, diff.old_path) unless file.exists?
+ - next unless file.exists?
+ .file{id: "diff-#{i}"}
+ .header
+ - 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-tiny pull-right 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-tiny pull-right view-file'} do
+ View file @
+ %span.commit-short-id= @commit.short_id(6)
+
+ .content
+ -# Skipp all non non-supported blobs
+ - next unless file.respond_to?('text?')
+ - if file.text?
+ = render "projects/commits/text_file", diff: diff, index: i
+ - elsif file.image?
+ - old_file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, 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
diff --git a/app/views/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 02debe426fe..c2da9f273b3 100644
--- a/app/views/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -4,15 +4,15 @@
= nav_link(controller: [:commit, :commits]) do
= link_to 'Commits', project_commits_path(@project, @repository.root_ref)
= nav_link(controller: :compare) do
- = link_to 'Compare', project_compare_index_path(@project)
+ = link_to 'Compare', project_compare_index_path(@project, from: @repository.root_ref, to: @ref || @repository.root_ref)
= nav_link(html_options: {class: branches_tab_class}) do
- = link_to project_repository_path(@project) do
+ = link_to recent_project_branches_path(@project) do
Branches
%span.badge= @repository.branches.length
- = nav_link(controller: :repositories, action: :tags) do
- = link_to tags_project_repository_path(@project) do
+ = nav_link(controller: :tags) do
+ = link_to project_tags_path(@project) do
Tags
%span.badge= @repository.tags.length
@@ -21,8 +21,7 @@
Stats
- - if current_controller?(:commits) && current_user.private_token
+ - if current_user && current_controller?(:commits) && current_user.private_token
%li.pull-right
- %span.rss-icon
- = link_to project_commits_path(@project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed" do
- = image_tag "rss_ui.png", title: "feed"
+ = link_to project_commits_path(@project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed" do
+ %i.icon-rss
diff --git a/app/views/commits/_image.html.haml b/app/views/projects/commits/_image.html.haml
index db02fa333b9..73f87289d3d 100644
--- a/app/views/commits/_image.html.haml
+++ b/app/views/projects/commits/_image.html.haml
@@ -9,7 +9,7 @@
%div.two-up.view
%span.wrap
.frame.deleted
- %a{href: project_tree_path(@project, tree_join(@commit.id, diff.old_path))}
+ %a{href: project_blob_path(@project, tree_join(@commit.parent_id, diff.old_path))}
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}"
@@ -21,7 +21,7 @@
%span.meta-height
%span.wrap
.frame.added
- %a{href: project_tree_path(@project, tree_join(@commit.id, diff.new_path))}
+ %a{href: project_blob_path(@project, tree_join(@commit.id, diff.new_path))}
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size file.size}"
diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml
new file mode 100644
index 00000000000..5be8460e061
--- /dev/null
+++ b/app/views/projects/commits/_inline_commit.html.haml
@@ -0,0 +1,9 @@
+%li.commit.inline-commit
+ .commit-row-title
+ = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
+ &nbsp;
+ = link_to_gfm truncate(commit.title, length: 40), project_commit_path(project, commit.id), class: "commit-row-message"
+ %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") }
+ = time_ago_in_words(commit.committed_date)
+ ago
+ &nbsp;
diff --git a/app/views/commits/_text_file.html.haml b/app/views/projects/commits/_text_file.html.haml
index 760fd07ed8b..c724213878a 100644
--- a/app/views/commits/_text_file.html.haml
+++ b/app/views/projects/commits/_text_file.html.haml
@@ -3,8 +3,8 @@
%a.supp_diff_link Diff 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|
- %tr.line_holder{ id: line_code }
+ - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line|
+ %tr.line_holder{ id: line_code, class: "#{type}" }
- if type == "match"
%td.old_line= "..."
%td.new_line= "..."
@@ -13,11 +13,11 @@
%td.old_line
= link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
- if @comments_allowed
- = render "notes/diff_note_link", line_code: line_code
+ = render "projects/notes/diff_note_link", line_code: line_code
%td.new_line= link_to raw(type == "old" ? "&nbsp;" : line_new) , "##{line_code}", id: line_code
%td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
- if @reply_allowed
- comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at)
- unless comments.empty?
- = render "notes/diff_notes_with_reply", notes: comments
+ = render "projects/notes/diff_notes_with_reply", notes: comments, line: line
diff --git a/app/views/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 46f9838e84a..46f9838e84a 100644
--- a/app/views/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
diff --git a/app/views/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index d180b8ec426..723c5a1c340 100644
--- a/app/views/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -1,8 +1,10 @@
= render "head"
- if @path.present?
- %ul.breadcrumb
- = breadcrumbs
+ %ul.breadcrumb.commit-breadcrumb
+ %li.light
+ History for
+ = commits_breadcrumbs
%div{id: dom_id(@project)}
#commits-list= render "commits"
@@ -11,7 +13,5 @@
- if @commits.count == @limit
:javascript
- $(function(){
- CommitsList.init("#{@ref}", #{@limit});
- });
+ CommitsList.init("#{@ref}", #{@limit});
diff --git a/app/views/commits/show.js.haml b/app/views/projects/commits/show.js.haml
index 797bc14cc1b..045c9dd83d7 100644
--- a/app/views/commits/show.js.haml
+++ b/app/views/projects/commits/show.js.haml
@@ -1,3 +1,3 @@
:plain
- CommitsList.append(#{@commits.count}, "#{escape_javascript(render(partial: 'commits/commits'))}");
+ CommitsList.append(#{@commits.count}, "#{escape_javascript(render('projects/commits/commits'))}");
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
new file mode 100644
index 00000000000..ca1ea4073e6
--- /dev/null
+++ b/app/views/projects/compare/_form.html.haml
@@ -0,0 +1,29 @@
+= form_tag project_compare_index_path(@project), method: :post do
+ .clearfix
+ .pull-left
+ - if params[:to] && params[:from]
+ = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'}
+ .input-prepend
+ %span.add-on.input-xpadding from
+ = text_field_tag :from, params[:from], class: "span3 input-xpadding"
+ = "..."
+ .input-prepend
+ %span.add-on.input-xpadding to
+ = text_field_tag :to, params[:to], class: "span3 input-xpadding"
+ .pull-left
+ &nbsp;
+ = submit_tag "Compare", class: "btn btn-create commits-compare-btn"
+ - if compare_to_mr_button?
+ = link_to compare_mr_path, class: 'prepend-left-10' do
+ %strong Make a merge request
+
+
+:javascript
+ var availableTags = #{@project.repository.ref_names.to_json};
+
+ $("#from, #to").autocomplete({
+ source: availableTags,
+ minLength: 1
+ });
+
+ disableButtonIfEmptyField('#to', '.commits-compare-btn');
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
new file mode 100644
index 00000000000..4745bfbeaaf
--- /dev/null
+++ b/app/views/projects/compare/index.html.haml
@@ -0,0 +1,16 @@
+= render "projects/commits/head"
+
+%h3.page-title
+ Compare View
+%p.slead
+ Compare branches, tags or commit ranges.
+ %br
+ Fill input field with commit id like
+ %code.label-branch 4eedf23
+ or branch/tag name like
+ %code.label-branch master
+ and press compare button for the commits list and a code diff.
+ %br
+ Changes are shown <b>from</b> the version in the first field <b>to</b> the version in the second field.
+
+= render "form"
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
new file mode 100644
index 00000000000..4be62d36917
--- /dev/null
+++ b/app/views/projects/compare/show.html.haml
@@ -0,0 +1,35 @@
+= render "projects/commits/head"
+
+%h3.page-title
+ Compare View
+
+= render "form"
+
+- if @commits.size > 100
+ .alert.alert-block
+ %p
+ %strong Warning! This comparison includes more than 100 commits.
+ %p To preserve performance the line diff is not shown.
+
+- if @commits.present?
+ %div.ui-box
+ .title
+ Commits (#{@commits.count})
+ %ul.well-list= render Commit.decorate(@commits), project: @project
+
+ - unless @diffs.empty?
+ %h4 Diff
+ = render "projects/commits/diffs", diffs: @diffs, project: @project
+- else
+ .light-well
+ %center
+ %h4
+ There isn't anything to compare.
+ %p.slead
+ - if params[:to] == params[:from]
+ You'll need to use different branch names to get a valid comparison.
+ - else
+ %span.label-branch #{params[:from]}
+ and
+ %span.label-branch #{params[:to]}
+ are the same.
diff --git a/app/views/projects/create.js.haml b/app/views/projects/create.js.haml
index 296c8688f47..a444b8b59a6 100644
--- a/app/views/projects/create.js.haml
+++ b/app/views/projects/create.js.haml
@@ -3,8 +3,7 @@
location.href = "#{project_path(@project)}";
- else
:plain
- $('.project_new_holder').show();
- $("#new_project").replaceWith("#{escape_javascript(render('new_form'))}");
+ $(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
+ $('.project-submit').enable();
$('.save-project-loader').hide();
- new Projects();
- $('select.chosen').chosen()
+ $('.project-edit-container').show();
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
new file mode 100644
index 00000000000..45f80ecd556
--- /dev/null
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -0,0 +1,25 @@
+%li
+ .pull-right
+ - if @available_keys.include?(deploy_key)
+ = link_to enable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do
+ %i.icon-plus
+ Enable
+ - else
+ - if deploy_key.projects.count > 1
+ = link_to disable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do
+ %i.icon-off
+ Disable
+ - else
+ = link_to 'Remove', project_deploy_key_path(@project, deploy_key), confirm: 'You are going to remove deploy key. Are you sure?', method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
+
+
+ = link_to project_deploy_key_path(deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first, deploy_key) do
+ %i.icon-key
+ %strong= deploy_key.title
+
+ %p.light.prepend-top-10
+ - deploy_key.projects.map(&:name_with_namespace).each do |project_name|
+ %span.label= project_name
+ %small.pull-right
+ Created #{time_ago_in_words(deploy_key.created_at)} ago
+
diff --git a/app/views/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index 5fb83021dc0..d49b2204537 100644
--- a/app/views/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -6,18 +6,18 @@
- @key.errors.full_messages.each do |msg|
%li= msg
- .clearfix
+ .control-group
= f.label :title
- .input= f.text_field :title
- .clearfix
+ .controls= f.text_field :title, class: 'input-xlarge'
+ .control-group
= f.label :key
- .input
- = f.text_area :key, class: [:xxlarge, :thin_area]
- %p.hint
- Paste a machine public key here. Read more about how generate it
+ .controls
+ %p.light
+ Paste a machine public key here. Read more about how to generate it
= link_to "here", help_ssh_path
+ = f.text_area :key, class: "input-xxlarge thin_area"
- .actions
- = f.submit 'Save', class: "btn-save btn"
+ .form-actions
+ = f.submit 'Create', class: "btn-create btn"
= link_to "Cancel", project_deploy_keys_path(@project), class: "btn btn-cancel"
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
new file mode 100644
index 00000000000..53d6e36c62c
--- /dev/null
+++ b/app/views/projects/deploy_keys/index.html.haml
@@ -0,0 +1,32 @@
+%h3.page-title
+ Deploy keys allow read-only access to the repository
+
+ = link_to new_project_deploy_key_path(@project), class: "btn btn-new pull-right", title: "New Deploy Key" do
+ %i.icon-plus
+ New Deploy Key
+
+%p.light
+ Deploy keys can be used for CI, staging or production servers.
+ You can create a deploy key or add an existing one
+
+%hr.clearfix
+
+.row
+ .span5.enabled-keys
+ %h5
+ %strong.cgreen Enabled deploy keys
+ for this project
+ %ul.bordered-list
+ = 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
+ .span5.available-keys
+ %h5
+ %strong Deploy keys
+ from projects available to you
+ %ul.bordered-list
+ = 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
diff --git a/app/views/projects/deploy_keys/new.html.haml b/app/views/projects/deploy_keys/new.html.haml
new file mode 100644
index 00000000000..186d6b58972
--- /dev/null
+++ b/app/views/projects/deploy_keys/new.html.haml
@@ -0,0 +1,4 @@
+%h3.page-title New Deploy key
+%hr
+
+= render 'form'
diff --git a/app/views/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml
index 227afecb061..49566f83d20 100644
--- a/app/views/deploy_keys/show.html.haml
+++ b/app/views/projects/deploy_keys/show.html.haml
@@ -1,14 +1,13 @@
-= render "repositories/head"
-%h3.page_title
+%h3.page-title
Deploy key:
= @key.title
%small
created at
= @key.created_at.stamp("Aug 21, 2011")
-.back_link
+.back-link
= link_to project_deploy_keys_path(@project) do
&larr; To keys list
%hr
%pre= @key.key
.pull-right
- = link_to 'Remove', project_deploy_key_path(@key.project, @key), confirm: 'Are you sure?', method: :delete, class: "btn-remove btn delete-key"
+ = link_to 'Remove', project_deploy_key_path(@project, @key), confirm: 'Are you sure?', method: :delete, class: "btn-remove btn delete-key"
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index fdd537da3aa..117ee13140e 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,12 +1,171 @@
-= render "project_head"
-.project_edit_holder
- %h3.page_title Edit Project
- %hr
- = render "projects/form"
-%div.save-project-loader.hide
+.project-edit-container
+ .project-edit-errors
+ .project-edit-content
+ %div
+ %h3.page-title
+ Project settings:
+ %p.light Some settings, such as "Transfer Project", are hidden inside the danger area below
+ %hr
+ .form-holder
+ = form_for(@project, remote: true) do |f|
+ %fieldset
+ .control-group.project_name_holder
+ = f.label :name do
+ Project name is
+ .controls
+ = f.text_field :name, placeholder: "Example Project", class: "span5"
+
+
+ .control-group
+ = f.label :description do
+ Project description
+ %span.light (optional)
+ .controls
+ = f.text_area :description, placeholder: "awesome project", class: "span5", rows: 3, maxlength: 250
+
+ - if @project.repository.exists? && @project.repository.branch_names.any?
+ .control-group
+ = f.label :default_branch, "Default Branch"
+ .controls= f.select(:default_branch, @repository.branch_names, {}, {class: 'chosen'})
+
+
+ - if can?(current_user, :change_public_mode, @project)
+ %fieldset.public-mode
+ %legend
+ Public mode:
+ .control-group
+ = f.label :public, class: 'control-label' do
+ %span Public access
+ .controls
+ = f.check_box :public
+ %span.descr
+ If checked, this project can be cloned
+ %em without any
+ authentication.
+ It will also be listed on the #{link_to "public access directory", public_root_path}.
+ %em Any
+ user will have #{link_to "Guest", help_permissions_path} permissions on the repository.
+
+ %fieldset.features
+ %legend
+ Labels:
+ .control-group
+ = f.label :label_list, "Labels", class: 'control-label'
+ .controls
+ = f.text_field :label_list, maxlength: 2000, class: "span5"
+ %p.hint Separate labels with commas.
+
+ %fieldset.features
+ %legend
+ Features:
+ .control-group
+ = f.label :issues_enabled, "Issues", class: 'control-label'
+ .controls
+ = f.check_box :issues_enabled
+ %span.descr Lightweight issue tracking system for this project
+
+ - if Project.issues_tracker.values.count > 1
+ .control-group
+ = f.label :issues_tracker, "Issues tracker", class: 'control-label'
+ .controls= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled })
+
+ .control-group
+ = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
+ .controls= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id?
+
+ .control-group
+ = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label'
+ .controls
+ = f.check_box :merge_requests_enabled
+ %span.descr Submit changes to be merged upstream.
+
+ .control-group
+ = f.label :wiki_enabled, "Wiki", class: 'control-label'
+ .controls
+ = f.check_box :wiki_enabled
+ %span.descr Pages for project documentation
+
+ .control-group
+ = f.label :wall_enabled, "Wall", class: 'control-label'
+ .controls
+ = f.check_box :wall_enabled
+ %span.descr Simple chat system for broadcasting inside project
+
+ .control-group
+ = f.label :snippets_enabled, "Snippets", class: 'control-label'
+ .controls
+ = f.check_box :snippets_enabled
+ %span.descr Share code pastes with others out of git repository
+
+
+ .form-actions
+ = f.submit 'Save changes', class: "btn btn-save"
+
+
+
+ %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, :change_namespace, @project)
+ .ui-box.ui-box-danger
+ .title Transfer project
+ .errors-holder
+ .form-holder
+ = form_for(@project, url: transfer_project_path(@project), remote: true, html: { class: 'transfer-project' }) do |f|
+ .control-group
+ = f.label :namespace_id do
+ %span Namespace
+ .controls
+ .control-group
+ = f.select :namespace_id, namespaces_options(@project.namespace_id), {prompt: 'Choose a project namespace'}, {class: 'chosen'}
+ %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
+ %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) do |f|
+ .control-group
+ = f.label :path do
+ %span Path
+ .controls
+ .control-group
+ = f.text_field :path
+ %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
+ .ui-box-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, confirm: remove_project_message(@project), method: :delete, class: "btn btn-remove"
+ - else
+ %p.nothing_here_message Only project owner can remove a project
+
+.save-project-loader.hide
%center
= image_tag "ajax_loader.gif"
- %h3 Saving project. Please wait a few minutes
-
-:javascript
- $(function(){ new Projects(); });
+ %h3 Saving project.
+ %p Please wait a moment, this page will automatically refresh when ready.
diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml
new file mode 100644
index 00000000000..b1fb4f8637d
--- /dev/null
+++ b/app/views/projects/edit_tree/show.html.haml
@@ -0,0 +1,46 @@
+%h3.page-title Edit mode
+.file-editor
+ = form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do
+ .file-holder
+ .file-title
+ %i.icon-file
+ %span.file_name
+ = @path
+ %small
+ on
+ %strong= @ref
+ %span.options
+ .btn-group.tree-btn-group
+ = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-tiny btn-cancel", confirm: leave_edit_message
+ .file-content.code
+ %pre#editor= @blob.data
+
+ .control-group.commit_message-group
+ = label_tag 'commit_message', class: "control-label" do
+ Commit message
+ .controls
+ = text_area_tag 'commit_message', '', placeholder: "Update #{@blob.name}", required: true, rows: 3
+ .form-actions
+ = hidden_field_tag 'last_commit', @last_commit
+ = hidden_field_tag 'content', '', id: "file-content"
+ .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", confirm: leave_edit_message
+
+:javascript
+ ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict")
+ var ace_mode = "#{@blob.language.try(:ace_mode)}";
+ var editor = ace.edit("editor");
+ if (ace_mode) {
+ editor.getSession().setMode('ace/mode/' + ace_mode);
+ }
+
+ disableButtonIfEmptyField("#commit_message", ".js-commit-button");
+
+ $(".js-commit-button").click(function(){
+ $("#file-content").val(editor.getValue());
+ $(".file-editor form").submit();
+ });
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 22aaaf0f2b2..9f3502e90de 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,35 +1,47 @@
-= render "project_head"
= render 'clone_panel'
-%div.git-empty
- %fieldset
- %legend Git global setup:
- %pre.dark
- = preserve do
- git config --global user.name "#{current_user.name}"
- git config --global user.email "#{current_user.email}"
+- if @project.import? && !@project.imported
+ .save-project-loader
+ %center
+ = image_tag "ajax_loader.gif"
+ %h3 Importing repository.
+ %p.monospace git clone --bare #{@project.import_url}
+ %p Please wait until we import repository for you. Refresh at will.
+ :javascript
+ new ProjectImport();
- %fieldset
- %legend Create Repository
- %pre.dark
- = preserve do
- mkdir #{@project.path}
- cd #{@project.path}
- git init
- touch README
- git add README
- git commit -m 'first commit'
- git remote add origin #{@project.url_to_repo}
- git push -u origin master
+- 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 Existing Git Repo?
- %pre.dark
- = preserve do
- cd existing_git_repo
- git remote add origin #{@project.url_to_repo}
- 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'
+ %span.clone= "git remote add origin #{@project.url_to_repo}"
+ :preserve
+ git push -u origin master
+
+ %fieldset
+ %legend Existing Git Repo?
+ %pre.dark
+ :preserve
+ cd existing_git_repo
+ %span.clone= "git remote add origin #{@project.url_to_repo}"
+ :preserve
+ git push -u origin master
- if can? current_user, :remove_project, @project
.prepend-top-20
- = link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn btn-remove pull-right"
+ = link_to 'Remove project', @project, confirm: remove_project_message(@project), method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/files.html.haml b/app/views/projects/files.html.haml
deleted file mode 100644
index d108308318e..00000000000
--- a/app/views/projects/files.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-= render "project_head"
-- unless @notes.empty?
- %table
- %thead
- %tr
- %th File name
- %th
-
- - @notes.each do |note|
- %tr
- %td
- %a{href: note.attachment.url}
- = image_tag gravatar_icon(note.author_email), class: "avatar s24"
- = note.attachment_identifier
- %td
- Added
- = time_ago_in_words(note.created_at)
- ago
-- else
- %p.slead All files attached to project wall, issues etc will be displayed here
-
-
diff --git a/app/views/projects/fork.html.haml b/app/views/projects/fork.html.haml
new file mode 100644
index 00000000000..a1c109e5d62
--- /dev/null
+++ b/app/views/projects/fork.html.haml
@@ -0,0 +1,19 @@
+.alert.alert-error.alert-block
+ %h4
+ %i.icon-code-fork
+ Fork Error!
+ %p
+ You are trying to fork
+ = link_to_project @project
+ but it fails due to next reason:
+
+
+ - if @forked_project && @forked_project.errors.any?
+ %p
+ &ndash;
+ = @forked_project.errors.full_messages.first
+
+ %p
+ = link_to fork_project_path(@project), title: "Fork", class: "btn", method: "POST" do
+ %i.icon-code-fork
+ Try to Fork again
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
new file mode 100644
index 00000000000..a21cb9e7861
--- /dev/null
+++ b/app/views/projects/graphs/show.html.haml
@@ -0,0 +1,34 @@
+.loading-graph
+ %center
+ .loading
+ %h3.page-title Building repository graph.
+ %p Please wait a moment, this page will automatically refresh when ready.
+
+.stat-graph
+ .header.clearfix
+ .pull-right
+ %select
+ %option{:value => "commits"} Commits
+ %option{:value => "additions"} Additions
+ %option{:value => "deletions"} Deletions
+ %h3#date_header.page-title
+ %p.light
+ Commits to #{@project.default_branch}, excluding merge commits. Limited by 6,000 commits
+ %input#brush_change{:type => "hidden"}
+ .graphs
+ #contributors-master
+ #contributors.clearfix
+ %ol.contributors-list.clearfix
+
+:javascript
+ $(".stat-graph").hide();
+
+ $.ajax({
+ type: "GET",
+ url: location.href,
+ complete: function() {
+ $(".stat-graph").fadeIn();
+ $(".loading-graph").hide();
+ },
+ dataType: "script"
+ });
diff --git a/app/views/projects/graphs/show.js.haml b/app/views/projects/graphs/show.js.haml
new file mode 100644
index 00000000000..43e776e5330
--- /dev/null
+++ b/app/views/projects/graphs/show.js.haml
@@ -0,0 +1,19 @@
+- if @success
+ :plain
+ controller = new ContributorsStatGraph
+ controller.init(#{@log})
+
+ $("select").change( function () {
+ var field = $(this).val()
+ controller.set_current_field(field)
+ controller.redraw_master()
+ controller.redraw_authors()
+ })
+
+ $("#brush_change").change( function () {
+ controller.change_date_header()
+ controller.redraw_authors()
+ })
+- else
+ :plain
+ $('.stat-graph').replaceWith('<div class="alert alert-error">Failed to load graph</div>')
diff --git a/app/views/hooks/_data_ex.html.erb b/app/views/projects/hooks/_data_ex.html.erb
index b4281fa18c7..b4281fa18c7 100644
--- a/app/views/hooks/_data_ex.html.erb
+++ b/app/views/projects/hooks/_data_ex.html.erb
diff --git a/app/views/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 88a5a7dcffe..f748eb29294 100644
--- a/app/views/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -1,42 +1,35 @@
-= render "projects/project_head"
+%h3.page-title
+ Post-receive hooks
-- if can? current_user, :admin_project, @project
- .alert.alert-info
- %span
- Post receive hooks for binding events when someone push to repository.
- %br
- Read more about web hooks
- %strong #{link_to "here", help_web_hooks_path, class: "vlink"}
+%p.light
+ #{link_to "Post-receive hooks ", help_web_hooks_path, class: "vlink"} can be
+ used for binding events when someone pushes to the repository.
+
+%hr.clearfix
= form_for [@project, @hook], as: :hook, url: project_hooks_path(@project), html: { class: 'form-inline' } do |f|
-if @hook.errors.any?
.alert.alert-error
- @hook.errors.full_messages.each do |msg|
%p= msg
- .clearfix
+ .control-group
= f.label :url, "URL:"
- .input
- = f.text_field :url, class: "text_field xxlarge"
+ .controls
+ = f.text_field :url, class: "text_field input-xxlarge input-xpadding", placeholder: 'http://example.com/trigger-ci.json'
&nbsp;
- = f.submit "Add Web Hook", class: "btn btn-primary"
+ = f.submit "Add Web Hook", class: "btn btn-create"
%hr
-if @hooks.any?
- %h3.page_title
- Hooks (#{@hooks.count})
- %br
- %table
- %thead
- %tr
- %th URL
- %th
- - @hooks.each do |hook|
- %tr
- %td
+ .ui-box
+ .title
+ Hooks (#{@hooks.count})
+ %ul.well-list
+ - @hooks.each do |hook|
+ %li
%span.badge.badge-info POST
&rarr;
%span.monospace= hook.url
- %td
.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), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove btn-small grouped"
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
new file mode 100644
index 00000000000..6acad9134d1
--- /dev/null
+++ b/app/views/projects/issues/_form.html.haml
@@ -0,0 +1,95 @@
+%div.issue-form-holder
+ %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.id}"
+ = form_for [@project, @issue] do |f|
+ -if @issue.errors.any?
+ .alert.alert-error
+ - @issue.errors.full_messages.each do |msg|
+ %span= msg
+ %br
+ .ui-box.ui-box-show
+ .ui-box-head
+ .control-group
+ = f.label :title do
+ %strong= "Subject *"
+ .controls
+ = f.text_field :title, maxlength: 255, class: "input-xxlarge js-gfm-input", autofocus: true, required: true
+ .ui-box-body
+ .control-group
+ .issue_assignee.pull-left
+ = f.label :assignee_id do
+ %i.icon-user
+ Assign to
+ .controls
+ .pull-left
+ = f.select(:assignee_id, @project.team.members.sort_by(&:name).map {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'})
+ .pull-right
+ &nbsp;
+ = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
+ .issue_milestone.pull-left
+ = f.label :milestone_id do
+ %i.icon-time
+ Milestone
+ .controls= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
+
+ .ui-box-bottom
+ .control-group
+ = f.label :label_list do
+ %i.icon-tag
+ Labels
+ .controls
+ = f.text_field :label_list, maxlength: 2000, class: "input-xxlarge"
+ %p.hint Separate labels with commas.
+
+ .control-group
+ = f.label :description, "Details"
+ .controls
+ = f.text_area :description, class: "input-xxlarge js-gfm-input", rows: 14
+ %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
+
+
+ .form-actions
+ - if @issue.new_record?
+ = f.submit 'Submit new issue', class: "btn btn-create"
+ -else
+ = f.submit 'Save changes', class: "btn-save btn"
+
+ - cancel_path = @issue.new_record? ? project_issues_path(@project) : project_issue_path(@project, @issue)
+ = link_to "Cancel", cancel_path, class: 'btn btn-cancel'
+
+
+
+
+:javascript
+ $("#issue_label_list")
+ .bind( "keydown", function( event ) {
+ if ( event.keyCode === $.ui.keyCode.TAB &&
+ $( this ).data( "autocomplete" ).menu.active ) {
+ event.preventDefault();
+ }
+ })
+ .bind("click", function(event) {
+ $(this).autocomplete("search", "");
+ })
+ .autocomplete({
+ minLength: 0,
+ source: function( request, response ) {
+ response( $.ui.autocomplete.filter(
+ #{raw labels_autocomplete_source}, extractLast( request.term ) ) );
+ },
+ focus: function() {
+ return false;
+ },
+ select: function(event, ui) {
+ var terms = split( this.value );
+ terms.pop();
+ terms.push( ui.item.value );
+ terms.push( "" );
+ this.value = terms.join( ", " );
+ return false;
+ }
+ });
+
+ $('.assign-to-me-link').on('click', function(e){
+ $('#issue_assignee_id').val("#{current_user.id}").trigger("chosen:updated");
+ e.preventDefault();
+ });
diff --git a/app/views/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index 7e0b2cde074..438cc02b477 100644
--- a/app/views/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -5,7 +5,7 @@
= link_to 'Milestones', project_milestones_path(@project), class: "tab"
= nav_link(controller: :labels) do
= link_to 'Labels', project_labels_path(@project), class: "tab"
- %li.pull-right
- %span.rss-icon
+ - if current_user
+ %li.pull-right
= link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
- = image_tag "rss_ui.png", title: "feed"
+ %i.icon-rss
diff --git a/app/views/issues/_show.html.haml b/app/views/projects/issues/_issue.html.haml
index fa888618066..b9a2c18efdc 100644
--- a/app/views/issues/_show.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,39 +1,44 @@
%li{ id: dom_id(issue), class: issue_css_classes(issue), url: project_issue_path(issue.project, issue) }
- if controller.controller_name == 'issues'
- .issue_check
+ .issue-check
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue)
- .pull-right
- - if issue.notes.any?
- %span.btn.btn-small.disabled.grouped
- %i.icon-comment
- = issue.notes.count
- - if can? current_user, :modify_issue, issue
- - if issue.closed
- = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {closed: false }, status_only: true), method: :put, class: "btn btn-small grouped reopen_issue", remote: true
- - else
- = link_to 'Close', project_issue_path(issue.project, issue, issue: {closed: true }, 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
- %i.icon-edit
- Edit
-
- - if issue.assignee
- = image_tag gravatar_icon(issue.assignee_email), class: "avatar"
- - else
- = image_tag "no_avatar.png", class: "avatar"
- %p= link_to_gfm truncate(issue.title, length: 100), project_issue_path(issue.project, issue), class: "row_title"
+ .issue-title
+ %span.light= "##{issue.iid}"
+ = link_to_gfm truncate(issue.title, length: 100), project_issue_path(issue.project, issue), class: "row_title"
- %span.update-author
- %span.cdark= "##{issue.id}"
+ .issue-info
- if issue.assignee
- assigned to #{issue.assignee_name}
+ assigned to #{link_to_member(@project, issue.assignee)}
- else
- &nbsp;
-
+ unassigned
- if issue.votes_count > 0
= render 'votes/votes_inline', votable: issue
- %span
+ - if issue.notes.any?
+ %span
+ %i.icon-comments
+ = issue.notes.count
+ - if issue.milestone
+ %span
+ %i.icon-time
+ = issue.milestone.title
+ .pull-right
+ %small updated #{time_ago_in_words(issue.updated_at)} ago
+
+ .issue-labels
- issue.labels.each do |label|
- %span.label
+ %span{class: "label #{label_css_class(label.name)}"}
%i.icon-tag
= label.name
+
+ .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
+ - 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
+ %i.icon-edit
+ Edit
+
+
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
new file mode 100644
index 00000000000..539c45edd94
--- /dev/null
+++ b/app/views/projects/issues/_issues.html.haml
@@ -0,0 +1,93 @@
+.ui-box
+ .title
+ = 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]', options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id]), prompt: "Assignee")
+ = select_tag('update[milestone_id]', options_from_collection_for_select(project_active_milestones, "id", "title", params[:milestone_id]), 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 gravatar_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
+
+
+ %ul.well-list.issues-list
+ = render @issues
+ - if @issues.blank?
+ %li
+ %h4.nothing_here_message No issues to show
+
+- if @issues.present?
+ .pull-right
+ %span.issue_counter #{@issues.total_count}
+ issues for this filter
+
+ = paginate @issues, remote: true, theme: "gitlab"
diff --git a/app/views/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml
index b1bc3ba0eba..b1bc3ba0eba 100644
--- a/app/views/issues/edit.html.haml
+++ b/app/views/projects/issues/edit.html.haml
diff --git a/app/views/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 00ddd4bf702..00ddd4bf702 100644
--- a/app/views/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
new file mode 100644
index 00000000000..81594b4972e
--- /dev/null
+++ b/app/views/projects/issues/index.html.haml
@@ -0,0 +1,23 @@
+= render "head"
+.issues_content
+ %h3.page-title
+ Issues
+ %span (<span class=issue_counter>#{@issues.total_count}</span>)
+ .pull-right
+ .span6
+ - 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 pull-right", title: "New Issue", id: "new_issue_link" do
+ %i.icon-plus
+ New Issue
+ = form_tag project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: 'pull-right' do
+ = hidden_field_tag :status, params[:status], id: 'search_status'
+ = hidden_field_tag :assignee_id, params[:assignee_id], id: 'search_assignee_id'
+ = hidden_field_tag :milestone_id, params[:milestone_id], id: 'search_milestone_id'
+ = hidden_field_tag :label_name, params[:label_name], id: 'search_label_name'
+ = search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'input-xpadding issue_search input-xlarge append-right-10 search-text-input' }
+
+.row
+ .span3
+ = render 'shared/project_filter', project_entities_path: project_issues_path(@project)
+ .span9.issues-holder
+ = render "issues"
diff --git a/app/views/projects/issues/index.js.haml b/app/views/projects/issues/index.js.haml
new file mode 100644
index 00000000000..1be6a64f535
--- /dev/null
+++ b/app/views/projects/issues/index.js.haml
@@ -0,0 +1,4 @@
+:plain
+ $('.issues-holder').html("#{escape_javascript(render('issues'))}");
+ History.replaceState({path: "#{request.url}"}, document.title, "#{request.url}");
+ Issues.reload();
diff --git a/app/views/issues/new.html.haml b/app/views/projects/issues/new.html.haml
index b1bc3ba0eba..b1bc3ba0eba 100644
--- a/app/views/issues/new.html.haml
+++ b/app/views/projects/issues/new.html.haml
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
new file mode 100644
index 00000000000..6d1a088721c
--- /dev/null
+++ b/app/views/projects/issues/show.html.haml
@@ -0,0 +1,74 @@
+%h3.page-title
+ Issue ##{@issue.iid}
+
+ %small
+ created at
+ = @issue.created_at.stamp("Aug 21, 2011")
+
+ %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
+ %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"
+ - 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 edit_project_issue_path(@project, @issue), class: "btn grouped" do
+ %i.icon-edit
+ Edit
+
+.pull-right
+ .span3#votes= render 'votes/votes_block', votable: @issue
+
+.back-link
+ = link_to project_issues_path(@project) do
+ &larr; To issues list
+
+
+.ui-box.ui-box-show
+ .ui-box-head
+ %h4.box-title
+ - if @issue.closed?
+ .error.status_info Closed
+ = gfm escape_once(@issue.title)
+
+ .ui-box-body
+ %cite.cgray
+ Created by #{link_to_member(@project, @issue.author)}
+ - if @issue.assignee
+ \ and currently assigned to #{link_to_member(@project, @issue.assignee)}
+
+ - if @issue.milestone
+ - milestone = @issue.milestone
+ %cite.cgray and attached to milestone
+ %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
+
+ .pull-right
+ - @issue.labels.each do |label|
+ %span{class: "label #{label_css_class(label.name)}"}
+ %i.icon-tag
+ = label.name
+ &nbsp;
+
+ - if @issue.description.present?
+ .ui-box-bottom
+ .wiki
+ = preserve do
+ = markdown @issue.description
+
+- 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"
+ - 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"
+
+.participants
+ %cite.cgray #{@issue.participants.count} participants
+ - @issue.participants.each do |participant|
+ = link_to_member(@project, participant, name: false, size: 24)
+
+.voting_notes#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index 7f66022a2de..7f66022a2de 100644
--- a/app/views/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
new file mode 100644
index 00000000000..2b1aafc546b
--- /dev/null
+++ b/app/views/projects/labels/_label.html.haml
@@ -0,0 +1,15 @@
+- frequency = @project.issues.tagged_with(label.name).count
+%li
+ %strong
+ %span{class: "label #{label_css_class(label.name)}"}
+ %i.icon-tag
+ - if frequency.zero?
+ %span.light= label.name
+ - else
+ = label.name
+ .pull-right
+ - unless frequency.zero?
+ = link_to project_issues_path(label_name: label.name) do
+ %strong
+ = pluralize(frequency, 'issue')
+ = "»"
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
new file mode 100644
index 00000000000..a058d09a1ce
--- /dev/null
+++ b/app/views/projects/labels/index.html.haml
@@ -0,0 +1,10 @@
+= render "projects/issues/head"
+
+- if @labels.present?
+ %ul.bordered-list.labels-table
+ - @labels.each do |label|
+ = render 'label', label: label
+
+- 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
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
new file mode 100644
index 00000000000..ce72756303e
--- /dev/null
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -0,0 +1,87 @@
+= form_for [@project, @merge_request], html: { class: "#{controller.action_name}-merge-request form-horizontal" } do |f|
+ -if @merge_request.errors.any?
+ .alert.alert-error
+ %ul
+ - @merge_request.errors.full_messages.each do |msg|
+ %li= msg
+
+ .merge-request-branches
+ .row
+ .span5
+ .clearfix
+ .pull-left
+ = f.select(:source_project_id,[[@merge_request.source_project.path_with_namespace,@merge_request.source_project.id]] , {}, {class: 'source_project chosen span3'})
+ .pull-left
+ &nbsp;
+ %i.icon-code-fork
+ = f.select(:source_branch, @merge_request.source_project.repository.branch_names, { include_blank: "Select branch" }, {class: 'source_branch chosen span2'})
+ .mr_source_commit.prepend-top-10
+ .span2
+ %h2.merge-request-angle.light
+ %i.icon-long-arrow-right
+ .span5
+ .clearfix
+ .pull-left
+ - projects = @project.forked_from_project.nil? ? [@project] : [ @project,@project.forked_from_project]
+ = f.select(:target_project_id, projects.map { |proj| [proj.path_with_namespace,proj.id] }, {include_blank: "Select Target Project" }, {class: 'target_project chosen span3'})
+ .pull-left
+ &nbsp;
+ %i.icon-code-fork
+ = f.select(:target_branch, @target_branches, { include_blank: "Select branch" }, {class: 'target_branch chosen span2'})
+ .mr_target_commit.prepend-top-10
+
+ %hr
+ .merge-request-form-info
+ .control-group
+ = f.label :title do
+ %strong= "Title *"
+ .controls= f.text_field :title, class: "input-xxlarge pad js-gfm-input", maxlength: 255, rows: 5, required: true
+ .control-group
+ .left
+ = f.label :assignee_id do
+ %i.icon-user
+ Assign to
+ .controls= f.select(:assignee_id, @project.team.members.sort_by(&:name).map {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'})
+ .left
+ = f.label :milestone_id do
+ %i.icon-time
+ Milestone
+ .controls= f.select(:milestone_id, @project.milestones.active.all.map {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
+ .control-group
+ = f.label :description, "Description"
+ .controls
+ = f.text_area :description, class: "input-xxlarge js-gfm-input", rows: 14
+ %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
+
+
+ .form-actions
+ - if @merge_request.new_record?
+ = f.submit 'Submit merge request', class: "btn btn-create"
+ -else
+ = f.submit 'Save changes', class: "btn btn-save"
+ - if @merge_request.new_record?
+ = link_to project_merge_requests_path(@source_project), class: "btn btn-cancel" do
+ Cancel
+ - else
+ = link_to project_merge_request_path(@target_project, @merge_request), class: "btn btn-cancel" do
+ Cancel
+
+:javascript
+ disableButtonIfEmptyField("#merge_request_title", ".btn-save");
+
+ var source_branch = $("#merge_request_source_branch")
+ , target_branch = $("#merge_request_target_branch")
+ , target_project = $("#merge_request_target_project_id");
+
+ $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: source_branch.val() });
+ $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
+
+ target_project.on("change", function() {
+ $.get("#{update_branches_project_merge_requests_path(@source_project)}", {target_project_id: $(this).val() });
+ });
+ source_branch.on("change", function() {
+ $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: $(this).val() });
+ });
+ target_branch.on("change", function() {
+ $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
+ });
diff --git a/app/views/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml
index 35a86e6511c..35a86e6511c 100644
--- a/app/views/merge_requests/_head.html.haml
+++ b/app/views/projects/merge_requests/_head.html.haml
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
new file mode 100644
index 00000000000..933d78bcbfb
--- /dev/null
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -0,0 +1,37 @@
+%li{ class: mr_css_classes(merge_request) }
+ .merge-request-title
+ %span.light= "##{merge_request.iid}"
+ = link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title"
+ - if merge_request.merged?
+ %small.pull-right
+ %i.icon-ok
+ = "MERGED"
+ - else
+ %span.pull-right
+ - if merge_request.for_fork?
+ %span.light
+ = "#{merge_request.source_project.path_with_namespace}"
+ = "#{merge_request.source_branch}"
+ %i.icon-angle-right.light
+ = "#{merge_request.target_branch}"
+ - else
+ = "#{merge_request.source_branch}"
+ %i.icon-angle-right.light
+ = "#{merge_request.target_branch}"
+ .merge-request-info
+ - if merge_request.author
+ authored by #{link_to_member(merge_request.source_project, merge_request.author)}
+ - if merge_request.votes_count > 0
+ = render 'votes/votes_inline', votable: merge_request
+ - if merge_request.notes.any?
+ %span
+ %i.icon-comments
+ = merge_request.mr_and_commit_notes.count
+ - if merge_request.milestone_id?
+ %span
+ %i.icon-time
+ = merge_request.milestone.title
+
+
+ .pull-right
+ %small updated #{time_ago_in_words(merge_request.updated_at)} ago
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
new file mode 100644
index 00000000000..47c14abdedd
--- /dev/null
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -0,0 +1,38 @@
+.merge-request
+ = render "projects/merge_requests/show/mr_title"
+ = render "projects/merge_requests/show/how_to_merge"
+ = render "projects/merge_requests/show/mr_box"
+ = render "projects/merge_requests/show/mr_accept"
+ - if @merge_request.source_project.gitlab_ci?
+ = render "projects/merge_requests/show/mr_ci"
+ = render "projects/merge_requests/show/commits"
+
+ - if @commits.present?
+ %ul.nav.nav-tabs
+ %li.notes-tab{data: {action: 'notes'}}
+ = link_to project_merge_request_path(@project, @merge_request) do
+ %i.icon-comment
+ Discussion
+ %li.diffs-tab{data: {action: 'diffs'}}
+ = link_to diffs_project_merge_request_path(@project, @merge_request) do
+ %i.icon-list-alt
+ Diff
+
+ .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
+ = render "projects/notes/notes_with_form"
+ .diffs.tab-content
+ - if current_page?(action: 'diffs')
+ = render "projects/merge_requests/show/diffs"
+ .status
+
+:javascript
+ var merge_request;
+
+ merge_request = new MergeRequest({
+ url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}",
+ check_enable: #{@merge_request.unchecked? ? "true" : "false"},
+ url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}",
+ ci_enable: #{@project.gitlab_ci? ? "true" : "false"},
+ current_status: "#{@merge_request.merge_status_name}",
+ action: "#{controller.action_name}"
+ });
diff --git a/app/views/merge_requests/automerge.js.haml b/app/views/projects/merge_requests/automerge.js.haml
index e01ff662e7d..e01ff662e7d 100644
--- a/app/views/merge_requests/automerge.js.haml
+++ b/app/views/projects/merge_requests/automerge.js.haml
diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml
new file mode 100644
index 00000000000..ec4d7f2121b
--- /dev/null
+++ b/app/views/projects/merge_requests/branch_from.js.haml
@@ -0,0 +1,7 @@
+:plain
+ $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project)}");
+ var mrTitle = $('#merge_request_title');
+
+ if(mrTitle.val().length == 0) {
+ mrTitle.val("#{params[:ref].titleize}");
+ }
diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml
new file mode 100644
index 00000000000..f4e2886ee44
--- /dev/null
+++ b/app/views/projects/merge_requests/branch_to.js.haml
@@ -0,0 +1,2 @@
+:plain
+ $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project)}");
diff --git a/app/views/merge_requests/commits.js.haml b/app/views/projects/merge_requests/commits.js.haml
index 923b1ea032f..923b1ea032f 100644
--- a/app/views/merge_requests/commits.js.haml
+++ b/app/views/projects/merge_requests/commits.js.haml
diff --git a/app/views/merge_requests/diffs.html.haml b/app/views/projects/merge_requests/diffs.html.haml
index 2a5b8b1441e..2a5b8b1441e 100644
--- a/app/views/merge_requests/diffs.html.haml
+++ b/app/views/projects/merge_requests/diffs.html.haml
diff --git a/app/views/merge_requests/diffs.js.haml b/app/views/projects/merge_requests/diffs.js.haml
index 266892c01ef..2964f0ec462 100644
--- a/app/views/merge_requests/diffs.js.haml
+++ b/app/views/projects/merge_requests/diffs.js.haml
@@ -1,2 +1,2 @@
:plain
- merge_request.$(".diffs").html("#{escape_javascript(render(partial: "merge_requests/show/diffs"))}");
+ merge_request.$(".diffs").html("#{escape_javascript(render(partial: "projects/merge_requests/show/diffs"))}");
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
new file mode 100644
index 00000000000..67a1541d9bf
--- /dev/null
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -0,0 +1,4 @@
+%h3.page-title
+ = "Edit merge request ##{@merge_request.id}"
+%hr
+= render 'form'
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
new file mode 100644
index 00000000000..2bd5a027a02
--- /dev/null
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -0,0 +1,76 @@
+- if can? current_user, :write_merge_request, @project
+ = link_to new_project_merge_request_path(@project), class: "pull-right btn btn-new", title: "New Merge Request" do
+ %i.icon-plus
+ New Merge Request
+%h3.page-title
+ Merge Requests
+ %span (#{@merge_requests.total_count})
+
+
+.row
+ .span3
+ = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project)
+ .span9
+ .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 gravatar_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
+
+ %ul.well-list.mr-list
+ = render @merge_requests
+ - if @merge_requests.blank?
+ %li
+ %h4.nothing_here_message No merge requests to show
+ - if @merge_requests.present?
+ .pull-right
+ %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter
+
+ = paginate @merge_requests, theme: "gitlab"
+
+:javascript
+ $(merge_requestsPage);
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
new file mode 100644
index 00000000000..c962811a3e4
--- /dev/null
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -0,0 +1,17 @@
+.merge-request
+ = render "projects/merge_requests/show/mr_title"
+ = render "projects/merge_requests/show/mr_box"
+
+ .alert.alert-error
+ %h5
+ %i.icon-exclamation-sign
+ We cannot find
+ %span.label-branch= @merge_request.source_branch
+ or
+ %span.label-branch= @merge_request.target_branch
+ branches in the repository.
+ %p
+ Maybe it was removed or never pushed.
+ %p
+ Please close Merge Request or change branches with existing one
+
diff --git a/app/views/projects/merge_requests/new.html.haml b/app/views/projects/merge_requests/new.html.haml
new file mode 100644
index 00000000000..8ee0e1a8d46
--- /dev/null
+++ b/app/views/projects/merge_requests/new.html.haml
@@ -0,0 +1,3 @@
+%h3.page-title New Merge Request
+%hr
+= render 'form'
diff --git a/app/views/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 2a5b8b1441e..2a5b8b1441e 100644
--- a/app/views/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
diff --git a/app/views/merge_requests/show.js.haml b/app/views/projects/merge_requests/show.js.haml
index 2ce6eb63290..2ce6eb63290 100644
--- a/app/views/merge_requests/show.js.haml
+++ b/app/views/projects/merge_requests/show.js.haml
diff --git a/app/views/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index 5e27b6dc25a..7b0e67053a5 100644
--- a/app/views/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -1,30 +1,30 @@
- if @commits.present?
.ui-box
- %h5.title
+ .title
%i.icon-list
Commits (#{@commits.count})
.commits
- if @commits.count > 8
%ul.first-commits.well-list
- @commits.first(8).each do |commit|
- = render "commits/commit", commit: commit
+ = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
%li.bottom
8 of #{@commits.count} commits displayed.
%strong
%a.show-all-commits Click here to show all
%ul.all-commits.hide.well-list
- @commits.each do |commit|
- = render "commits/commit", commit: commit
+ = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
- else
%ul.well-list
- @commits.each do |commit|
- = render "commits/commit", commit: commit
+ = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
- else
- %h5
+ %h4.nothing_here_message
Nothing to merge from
- %span.label #{@merge_request.source_branch}
+ %span.label-branch #{@merge_request.source_branch}
to
- %span.label #{@merge_request.target_branch}
+ %span.label-branch #{@merge_request.target_branch}
%br
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
new file mode 100644
index 00000000000..25f63804858
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -0,0 +1,10 @@
+- if @merge_request.valid_diffs?
+ = render "projects/commits/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project
+- elsif @merge_request.broken_diffs?
+ %h4.nothing_here_message
+ Can't load diff.
+ You can
+ = link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request), format: :diff, class: "vlink"
+ instead.
+- else
+ %h4.nothing_here_message Nothing to merge
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
new file mode 100644
index 00000000000..030ac285f2a
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -0,0 +1,51 @@
+%div#modal_merge_info.modal.hide
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3 How to merge
+ .modal-body
+ - if @merge_request.for_fork?
+ - source_remote = @merge_request.source_project.namespace.nil? ? "source" :@merge_request.source_project.namespace.path
+ - target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
+ %p
+ %strong Step 1.
+ Checkout target branch and get recent objects from GitLab
+ Assuming remote for #{@merge_request.target_project.path_with_namespace} is called #{target_remote}
+ remote for #{@merge_request.source_project.path_with_namespace} is called #{source_remote}
+ %pre.dark
+ :preserve
+ git checkout #{target_remote} #{@merge_request.target_branch}
+ git fetch #{source_remote}
+ %p
+ %strong Step 2.
+ Merge source branch into target branch and push changes to GitLab
+ %pre.dark
+ :preserve
+ git merge #{source_remote}/#{@merge_request.source_branch}
+ git push #{target_remote} #{@merge_request.target_branch}
+ - else
+ %p
+ %strong Step 1.
+ Checkout target branch and get recent objects from GitLab
+ %pre.dark
+ :preserve
+ git checkout #{@merge_request.target_branch}
+ git fetch origin
+ %p
+ %strong Step 2.
+ Merge source branch into target branch and push changes to GitLab
+ %pre.dark
+ :preserve
+ git merge origin/#{@merge_request.source_branch}
+ git push origin #{@merge_request.target_branch}
+
+
+:javascript
+ $(function(){
+ var modal = $('#modal_merge_info').modal({modal: true, show:false});
+ $('.how_to_merge_link').bind("click", function(){
+ modal.show();
+ });
+ $('.modal-header .close').bind("click", function(){
+ modal.hide();
+ })
+ })
diff --git a/app/views/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml
index c2c04b863e7..299e1bc1e08 100644
--- a/app/views/merge_requests/show/_mr_accept.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -1,9 +1,9 @@
-- unless can?(current_user, :accept_mr, @project)
+- unless @allowed_to_merge
.alert
- %strong Only masters can accept MR
+ %strong You don't have permission to merge this MR
-- if @merge_request.open? && @commits.any? && can?(current_user, :accept_mr, @project)
+- if @show_merge_controls
.automerge_widget.can_be_merged{style: "display:none"}
.alert.alert-success
%span
@@ -11,11 +11,12 @@
%p
You can accept this request automatically.
If you still want to do it manually -
- %strong= link_to "click here", "#", class: "how_to_merge_link vlink", title: "How To Merge"
+ %strong
+ = link_to "click here", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
for instructions
.accept_group
- = f.submit "Accept Merge Request", class: "btn success accept_merge_request"
- - unless @project.root_ref? @merge_request.source_branch
+ = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
+ - unless @merge_request.disallow_source_branch_removal?
.remove_branch_holder
= label_tag :should_remove_source_branch, class: "checkbox" do
= check_box_tag :should_remove_source_branch
@@ -26,12 +27,12 @@
.automerge_widget.no_satellite{style: "display:none"}
.alert.alert-error
%span
- %strong This repository does not have satellite. Ask administrator to fix this issue
+ %strong This repository does not have satellite. Ask an administrator to fix this issue
.automerge_widget.cannot_be_merged{style: "display:none"}
.alert.alert-disabled
%span
- = link_to "Show how to merge", "#", class: "how_to_merge_link btn btn-small padded", title: "How To Merge"
+ = link_to "Show how to merge", "#modal_merge_info", class: "how_to_merge_link btn padded", title: "How To Merge", "data-toggle" => "modal"
&nbsp;
%strong This request can't be merged with GitLab. You should do it manually
@@ -46,5 +47,7 @@
%strong This merge request already can not be merged. Try to reload page.
.merge-in-progress.hide
- %span.cgray Merge is in progress. Please wait. Page will be automatically reloaded. &nbsp;
- = image_tag "ajax_loader.gif"
+ %span.cgray
+ %i.icon-refresh.icon-spin
+ &nbsp;
+ Merge is in progress. Please wait. Page will be automatically reloaded. &nbsp;
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
new file mode 100644
index 00000000000..1f750e22c65
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -0,0 +1,48 @@
+.ui-box.ui-box-show
+ .ui-box-head
+ %h4.box-title
+ = gfm escape_once(@merge_request.title)
+ - if @merge_request.merged?
+ .success.status_info
+ %i.icon-ok
+ Merged
+ - elsif @merge_request.closed?
+ .error.status_info Closed
+
+ .ui-box-body
+ %div
+ %cite.cgray
+ Created at #{@merge_request.created_at.stamp("Aug 21, 2011")} 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
+ - milestone = @merge_request.milestone
+ %cite.cgray and attached to milestone
+ %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
+
+
+ - if @merge_request.description.present?
+ .ui-box-bottom
+ .wiki
+ = preserve do
+ = markdown @merge_request.description
+
+ - if @merge_request.closed?
+ .ui-box-bottom.alert-error
+ %span
+ %i.icon-remove
+ Closed by #{link_to_member(@project, @merge_request.closed_event.author)}
+ %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago.
+ - if @merge_request.merged?
+ .ui-box-bottom.alert-success
+ %span
+ %i.icon-ok
+ Merged by #{link_to_member(@project, @merge_request.merge_event.author)}
+ #{time_ago_in_words(@merge_request.merge_event.created_at)} ago.
+ - if !@closes_issues.empty? && @merge_request.opened?
+ .ui-box-bottom.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/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml
index dd1e78a0205..9b15c4526e9 100644
--- a/app/views/merge_requests/show/_mr_ci.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml
@@ -1,4 +1,4 @@
-- if @merge_request.open? && @commits.any?
+- if @commits.any?
.ci_widget.ci-success{style: "display:none"}
.alert.alert-success
%i.icon-ok
diff --git a/app/views/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 8119728dcb9..096a9167645 100644
--- a/app/views/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,13 +1,20 @@
-%h3.page_title
- = "Merge Request ##{@merge_request.id}:"
+%h3.page-title
+ = "Merge Request ##{@merge_request.iid}:"
&nbsp;
- %span.label_branch= @merge_request.source_branch
- &rarr;
- %span.label_branch= @merge_request.target_branch
+ -if @merge_request.for_fork?
+ %span.label-branch
+ %span.label-project= truncate(@merge_request.source_project.path_with_namespace, length: 25)
+ #{@merge_request.source_branch}
+ &rarr;
+ %span.label-branch= @merge_request.target_branch
+ - else
+ %span.label-branch= @merge_request.source_branch
+ &rarr;
+ %span.label-branch= @merge_request.target_branch
%span.pull-right
- if can?(current_user, :modify_merge_request, @merge_request)
- - if @merge_request.open?
+ - if @merge_request.opened?
.left.btn-group
%a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} }
%i.icon-download-alt
@@ -17,15 +24,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: {closed: true }, status_only: true), 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 grouped btn-close", title: "Close merge request"
- = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped" do
+ = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped", id:"edit_merge_request" do
%i.icon-edit
Edit
.pull-right
.span3#votes= render 'votes/votes_block', votable: @merge_request
-.back_link
+.back-link
= link_to project_merge_requests_path(@project) do
&larr; To merge requests
diff --git a/app/views/projects/merge_requests/update_branches.js.haml b/app/views/projects/merge_requests/update_branches.js.haml
new file mode 100644
index 00000000000..dfccb586ec7
--- /dev/null
+++ b/app/views/projects/merge_requests/update_branches.js.haml
@@ -0,0 +1,5 @@
+:plain
+ $(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}");
+ $(".target_branch").trigger("chosen:updated");
+ $(".mr_target_commit").html("");
+ $(".target_branch").trigger("change");
diff --git a/app/views/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index fbaf64a305c..78e4cd2243e 100644
--- a/app/views/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,5 +1,5 @@
-%h3.page_title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.id}"
-.back_link
+%h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.id}"
+.back-link
= link_to project_milestones_path(@project) do
&larr; To milestones
@@ -26,13 +26,13 @@
.span6
.control-group
= f.label :due_date, "Due Date", class: "control-label"
- .input= f.hidden_field :due_date
+ .controls= f.hidden_field :due_date
.controls
.datepicker
.form-actions
- if @milestone.new_record?
- = f.submit 'Create milestone', class: "btn-save btn"
+ = f.submit 'Create milestone', class: "btn-create btn"
= link_to "Cancel", project_milestones_path(@project), class: "btn btn-cancel"
-else
= f.submit 'Save changes', class: "btn-save btn"
@@ -40,10 +40,8 @@
:javascript
- $(function() {
- disableButtonIfEmptyField("#milestone_title", ".btn-save");
- $( ".datepicker" ).datepicker({
- dateFormat: "yy-mm-dd",
- onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
- }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
- });
+ disableButtonIfEmptyField("#milestone_title", ".btn-save");
+ $( ".datepicker" ).datepicker({
+ dateFormat: "yy-mm-dd",
+ onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
+ }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
diff --git a/app/views/projects/milestones/_issues.html.haml b/app/views/projects/milestones/_issues.html.haml
new file mode 100644
index 00000000000..bf81cfda45f
--- /dev/null
+++ b/app/views/projects/milestones/_issues.html.haml
@@ -0,0 +1,11 @@
+.ui-box
+ .title= title
+ %ul.well-list
+ - issues.each do |issue|
+ %li
+ = link_to [@project, issue] do
+ %span.badge{class: issue.closed? ? 'badge-important' : 'badge-info'} ##{issue.iid}
+ = link_to_gfm truncate(issue.title, length: 40), [@project, issue]
+ - if issue.assignee
+ .pull-right
+ = image_tag gravatar_icon(issue.assignee.email, 16), class: "avatar s16"
diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml
new file mode 100644
index 00000000000..7f815894069
--- /dev/null
+++ b/app/views/projects/milestones/_merge_request.html.haml
@@ -0,0 +1,5 @@
+%li
+ = link_to [@project, merge_request] do
+ %span.badge.badge-info ##{merge_request.id}
+ &ndash;
+ = link_to_gfm truncate(merge_request.title, length: 60), [@project, merge_request]
diff --git a/app/views/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 00e20117f36..bc3368b765c 100644
--- a/app/views/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,23 +1,21 @@
-%li{class: "milestone milestone-#{milestone.closed ? 'closed' : 'open'}", id: dom_id(milestone) }
+%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
.pull-right
- - if can?(current_user, :admin_milestone, milestone.project) and milestone.open?
+ - 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
%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"
%h4
= link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone)
- - if milestone.expired? and not milestone.closed
+ - if milestone.expired? and not milestone.closed?
%span.cred (Expired)
%small
= milestone.expires_at
- if milestone.is_empty?
%span.muted Empty
- else
- .row
- .span4
- .progress.progress-info
- .bar{style: "width: #{milestone.percent_complete}%;"}
- .span6
+ %div
+ %div
= link_to project_issues_path(milestone.project, milestone_id: milestone.id) do
= pluralize milestone.issues.count, 'Issue'
&nbsp;
@@ -25,3 +23,5 @@
= pluralize milestone.merge_requests.count, 'Merge Request'
&nbsp;
%span.light #{milestone.percent_complete}% complete
+ .progress.progress-info
+ .bar{style: "width: #{milestone.percent_complete}%;"}
diff --git a/app/views/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index b1bc3ba0eba..b1bc3ba0eba 100644
--- a/app/views/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
diff --git a/app/views/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index b78f17053fd..ddb46bcb5cb 100644
--- a/app/views/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,13 +1,15 @@
-= render "issues/head"
+= render "projects/issues/head"
.milestones_content
- %h3.page_title
+ %h3.page-title
Milestones
- if can? current_user, :admin_milestone, @project
- = link_to "New Milestone", new_project_milestone_path(@project), class: "pull-right btn btn-small", title: "New Milestone"
- %br
- %div.ui-box
- .title
- %ul.nav.nav-pills
+ = link_to new_project_milestone_path(@project), class: "pull-right btn btn-new", title: "New Milestone" do
+ %i.icon-plus
+ New Milestone
+
+ .row
+ .span3
+ %ul.nav.nav-pills.nav-stacked
%li{class: ("active" if (params[:f] == "active" || !params[:f]))}
= link_to project_milestones_path(@project, f: "active") do
Active
@@ -17,12 +19,13 @@
%li{class: ("active" if params[:f] == "all")}
= link_to project_milestones_path(@project, f: "all") do
All
+ .span9
+ .ui-box
+ %ul.well-list
+ = render @milestones
- %ul.well-list
- = render @milestones
+ - if @milestones.blank?
+ %li
+ %h3.nothing_here_message No milestones to show
- - if @milestones.present?
- %li.bottom= paginate @milestones, theme: "gitlab"
- - else
- %li
- %h3.nothing_here_message Nothing to show here
+ = paginate @milestones, theme: "gitlab"
diff --git a/app/views/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index b1bc3ba0eba..b1bc3ba0eba 100644
--- a/app/views/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
new file mode 100644
index 00000000000..b755a813419
--- /dev/null
+++ b/app/views/projects/milestones/show.html.haml
@@ -0,0 +1,105 @@
+= render "projects/issues/head"
+%h3.page-title
+ Milestone ##{@milestone.id}
+ %small
+ = @milestone.expires_at
+ .pull-right
+ - if can?(current_user, :admin_milestone, @project)
+ = link_to edit_project_milestone_path(@project, @milestone), class: "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"
+ - else
+ = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn"
+
+- if @milestone.issues.any? && @milestone.can_be_closed?
+ .alert.alert-success
+ %span All issues for this milestone are closed. You may close milestone now.
+
+.back-link
+ = link_to project_milestones_path(@project) do
+ &larr; To milestones list
+
+
+.ui-box.ui-box-show
+ .ui-box-head
+ %h4.box-title
+ - if @milestone.closed?
+ .error.status_info Closed
+ - elsif @milestone.expired?
+ .error.status_info Expired
+
+ = gfm escape_once(@milestone.title)
+
+ .ui-box-body
+ %p
+ Progress:
+ #{@milestone.closed_items_count} closed
+ &ndash;
+ #{@milestone.open_items_count} open
+ %span.pull-right= @milestone.expires_at
+ .progress.progress-info
+ .bar{style: "width: #{@milestone.percent_complete}%;"}
+
+
+ - if @milestone.description.present?
+ .ui-box-bottom
+ = preserve do
+ = markdown @milestone.description
+
+
+%ul.nav.nav-tabs
+ %li.active
+ = link_to '#tab-issues', 'data-toggle' => 'tab' do
+ Issues
+ %span.badge= @issues.count
+ %li
+ = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
+ Merge Requests
+ %span.badge= @merge_requests.count
+ %li
+ = link_to '#tab-participants', 'data-toggle' => 'tab' do
+ Participants
+ %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
+ %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"
+
+.tab-content
+ .tab-pane.active#tab-issues
+ .row
+ .span4
+ = render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned)
+ .span4
+ = render('issues', title: 'Ongoing Issues (open and assigned)', issues: @issues.opened.assigned)
+ .span4
+ = render('issues', title: 'Completed Issues (closed)', issues: @issues.closed)
+
+ .tab-pane#tab-merge-requests
+ .row
+ .span6
+ .ui-box
+ .title Open
+ %ul.well-list
+ - @merge_requests.opened.each do |merge_request|
+ = render 'merge_request', merge_request: merge_request
+ .span6
+ .ui-box
+ .title Closed
+ %ul.well-list
+ - @merge_requests.closed.each do |merge_request|
+ = render 'merge_request', merge_request: merge_request
+
+ .tab-pane#tab-participants
+ %ul.bordered-list
+ - @users.each do |user|
+ %li
+ = link_to user, title: user.name, class: "dark" do
+ = image_tag gravatar_icon(user.email, 32), class: "avatar s32"
+ %strong= truncate(user.name, lenght: 40)
+ %br
+ %small.cgray= user.username
diff --git a/app/views/projects/milestones/update.js.haml b/app/views/projects/milestones/update.js.haml
new file mode 100644
index 00000000000..3ff84915e97
--- /dev/null
+++ b/app/views/projects/milestones/update.js.haml
@@ -0,0 +1,2 @@
+:plain
+ $('##{dom_id(@milestone)}').fadeOut();
diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml
new file mode 100644
index 00000000000..2790ed6f594
--- /dev/null
+++ b/app/views/projects/network/_head.html.haml
@@ -0,0 +1,23 @@
+.clearfix
+ .pull-left
+ = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
+ .pull-left
+ = form_tag project_network_path(@project, @id), method: :get do |f|
+ .control-group
+ = label_tag :filter_ref, "Begin with the selected commit", class: 'control-label light'
+ .controls
+ = check_box_tag :filter_ref, 1, @options[:filter_ref]
+ - @options.each do |key, value|
+ = hidden_field_tag(key, value, id: nil) unless key == "filter_ref"
+
+ .search.pull-right
+ = form_tag project_network_path(@project, @id), method: :get do |f|
+ .control-group
+ = label_tag :search , "Looking for commit:", class: 'control-label light'
+ .controls
+ = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: "search-input input-xlarge"
+ = button_tag type: 'submit', class: 'btn vtop' do
+ %i.icon-search
+ - @options.each do |key, value|
+ = hidden_field_tag(key, value, id: nil) unless key == "extended_sha1"
+
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
new file mode 100644
index 00000000000..492f77341f7
--- /dev/null
+++ b/app/views/projects/network/show.html.haml
@@ -0,0 +1,14 @@
+= render "head"
+.project-network
+ .tip
+ You can move around the graph by using the arrow keys.
+ .network-graph
+ .loading.loading-gray
+
+:javascript
+ new Network({
+ url: '#{project_network_path(@project, @ref, @options.merge(format: :json))}',
+ commit_url: '#{project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")}',
+ ref: '#{@ref}',
+ commit_id: '#{@commit.id}'
+ })
diff --git a/app/views/projects/network/show.json.erb b/app/views/projects/network/show.json.erb
new file mode 100644
index 00000000000..f0bedcf2d35
--- /dev/null
+++ b/app/views/projects/network/show.json.erb
@@ -0,0 +1,23 @@
+<% self.formats = ["html"] %>
+
+<%= raw(
+ {
+ days: @graph.days.compact.map { |d| [d.day, d.strftime("%b")] },
+ commits: @graph.commits.map do |c|
+ {
+ parents: parents_zip_spaces(c.parents(@graph.map), c.parent_spaces),
+ author: {
+ name: c.author_name,
+ email: c.author_email,
+ icon: gravatar_icon(c.author_email, 20)
+ },
+ time: c.time,
+ space: c.spaces.first,
+ refs: get_refs(@graph.repo, c),
+ id: c.sha,
+ date: c.date,
+ message: c.message,
+ }
+ end
+ }.to_json
+) %>
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 933cb671142..0213576927b 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,12 +1,71 @@
-.project_new_holder
- %h3.page_title
- New Project
- %hr
- = render 'new_form'
-%div.save-project-loader.hide
+.project-edit-container
+ .project-edit-errors
+ = render 'projects/errors'
+ .project-edit-content
+
+ = form_for @project, remote: true do |f|
+ .control-group.project-name-holder
+ = f.label :name do
+ %strong Project name is
+ .controls
+ = f.text_field :name, placeholder: "Example Project", class: "input-xlarge", tabindex: 1, autofocus: true
+ %span.help-inline
+ = link_to "#", class: 'js-toggle-visibility-link' do
+ %span Customize repository name?
+
+ .control-group.js-toggle-visibility-container.hide
+ = f.label :path do
+ %span Repository name
+ .controls
+ .input-append
+ = f.text_field :path
+ %span.add-on .git
+
+
+ - if current_user.can_select_namespace?
+ .control-group
+ = f.label :namespace_id do
+ %span Namespace
+ .controls
+ = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen', tabindex: 2}
+
+ .control-group
+ .controls
+ = link_to "#", class: 'appear-link' do
+ %i.icon-upload-alt
+ %span Import existing repository?
+ .control-group.appear-data.import-url-data
+ = f.label :import_url do
+ %span Import existing repo
+ .controls
+ = f.text_field :import_url, class: 'input-xlarge', placeholder: 'https://github.com/randx/six.git'
+ .light
+ URL must be cloneable
+ .control-group
+ = f.label :description do
+ Description
+ %span.light (optional)
+ .controls
+ = f.text_area :description, placeholder: "awesome project", class: "input-xlarge", rows: 3, maxlength: 250, tabindex: 3
+ .control-group.project-public-holder
+ = f.label :public do
+ %span Public project
+ .controls
+ = f.check_box :public, { checked: Gitlab.config.gitlab.default_projects_features.public }, true, false
+ %span.help-inline Make project visible to everyone
+
+ .form-actions
+ = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
+
+ - if current_user.can_create_group?
+ .pull-right
+ .controls.light
+ Need a group for several dependent projects?
+ = link_to new_group_path, class: "btn btn-tiny" do
+ Create a group
+
+.save-project-loader.hide
%center
= image_tag "ajax_loader.gif"
- %h3 Creating project &amp; repository. Please wait a few minutes
-
-:javascript
- $(function(){ new Projects(); });
+ %h3 Creating project &amp; repository.
+ %p Please wait a moment, this page will automatically refresh when ready.
diff --git a/app/views/notes/_diff_note_link.html.haml b/app/views/projects/notes/_diff_note_link.html.haml
index 377c926a204..377c926a204 100644
--- a/app/views/notes/_diff_note_link.html.haml
+++ b/app/views/projects/notes/_diff_note_link.html.haml
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
new file mode 100644
index 00000000000..9537ab18caa
--- /dev/null
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -0,0 +1,13 @@
+- 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
+ %td.notes_line{ colspan: 2 }
+ %span.btn.disabled
+ %i.icon-comment
+ = notes.count
+ %td.notes_content
+ %ul.notes{ rel: note.discussion_id }
+ = render notes
+
+ = render "projects/notes/discussion_reply_button", note: note
diff --git a/app/views/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml
index 24cb4228174..a964d86a8dc 100644
--- a/app/views/notes/_discussion.html.haml
+++ b/app/views/projects/notes/_discussion.html.haml
@@ -8,7 +8,7 @@
= link_to "javascript:;", class: "js-details-target turn-off js-toggler-target" do
%i.icon-eye-open
Show discussion
- = image_tag gravatar_icon(note.author.email), class: "avatar s32"
+ = image_tag gravatar_icon(note.author_email), class: "avatar s32"
%div
= link_to_member(@project, note.author, avatar: false)
- if note.for_merge_request?
@@ -23,7 +23,7 @@
discussion on this merge request diff
- elsif note.for_commit?
started a discussion on commit
- #{link_to note.noteable.short_id, project_commit_path(@project, note.noteable)}
+ #{link_to note.noteable.short_id, project_commit_path(note.project, note.noteable)}
= link_to_commit_diff_line_note(note) if note.for_diff_line?
- else
%cite.cgray started a discussion
@@ -36,9 +36,9 @@
ago
.discussion-body
- if note.for_diff_line?
- - if note.diff
+ - if note.active?
.content
- .file= render "notes/discussion_diff", discussion_notes: discussion_notes, note: note
+ .file= 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
@@ -51,7 +51,7 @@
.content
.notes{ rel: discussion_notes.first.discussion_id }
= render discussion_notes
- = render "notes/discussion_reply_button", note: discussion_notes.first
+ = render "projects/notes/discussion_reply_button", note: discussion_notes.first
-# will be shown when the other one is hidden
.discussion-hidden.content.hide
diff --git a/app/views/notes/_discussion_diff.html.haml b/app/views/projects/notes/_discussion_diff.html.haml
index 790b77333d5..c3f41a1b6b5 100644
--- a/app/views/notes/_discussion_diff.html.haml
+++ b/app/views/projects/notes/_discussion_diff.html.haml
@@ -9,7 +9,7 @@
%br/
.content
%table
- - each_diff_line(diff, note.diff_file_index) do |line, type, line_code, line_new, line_old|
+ - 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= "..."
@@ -21,5 +21,4 @@
%td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} &nbsp;"
- if line_code == note.line_code
- = render "notes/diff_notes_with_reply", notes: discussion_notes
- - break # cut off diff after notes
+ = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/notes/_discussion_reply_button.html.haml b/app/views/projects/notes/_discussion_reply_button.html.haml
index d1c5ccc29db..d1c5ccc29db 100644
--- a/app/views/notes/_discussion_reply_button.html.haml
+++ b/app/views/projects/notes/_discussion_reply_button.html.haml
diff --git a/app/views/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index a154c31e5ab..7add2921830 100644
--- a/app/views/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note js-new-note-form" } do |f|
+= form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" } do |f|
= note_target_fields
= f.hidden_field :commit_id
@@ -22,18 +22,11 @@
.note-form-actions
.buttons
= f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button"
+ = yield(:note_actions)
+
%a.btn.grouped.js-close-discussion-note-form Cancel
.note-form-option
- = label_tag :notify do
- = check_box_tag :notify, 1, !@note.for_commit?
- %span.light Notify team via email
-
- .js-notify-commit-author
- = label_tag :notify_author do
- = check_box_tag :notify_author, 1 , @note.for_commit?
- %span.light Notify commit author
- .note-form-option
%a.choose-btn.btn.btn-small.js-choose-note-attachment-button
%i.icon-paper-clip
%span Choose File ...
diff --git a/app/views/notes/_form_errors.html.haml b/app/views/projects/notes/_form_errors.html.haml
index 0851536f0da..0b68bf243f0 100644
--- a/app/views/notes/_form_errors.html.haml
+++ b/app/views/projects/notes/_form_errors.html.haml
@@ -1,3 +1,3 @@
-.error_message.js-errors
+.error-message.js-errors
- note.errors.full_messages.each do |msg|
%div= msg
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
new file mode 100644
index 00000000000..324b698f3b5
--- /dev/null
+++ b/app/views/projects/notes/_note.html.haml
@@ -0,0 +1,66 @@
+%li{ id: dom_id(note), class: dom_class(note), data: { discussion: note.discussion_id } }
+ .note-header
+ .note-actions
+ = link_to "##{dom_id(note)}", name: dom_id(note) do
+ %i.icon-link
+ Link here
+ &nbsp;
+ - if(note.author_id == current_user.try(:id)) || can?(current_user, :admin_note, @project)
+ = link_to "#", title: "Edit comment", class: "js-note-edit" do
+ %i.icon-edit
+ Edit
+ &nbsp;
+ = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove this comment?', remote: true, class: "danger js-note-delete" do
+ %i.icon-trash.cred
+ Remove
+ = image_tag gravatar_icon(note.author_email), class: "avatar s32"
+ = link_to_member(@project, note.author, avatar: false)
+ %span.note-last-update
+ = note_timestamp(note)
+
+ - if note.upvote?
+ %span.vote.upvote.label.label-success
+ %i.icon-thumbs-up
+ \+1
+ - if note.downvote?
+ %span.vote.downvote.label.label-error
+ %i.icon-thumbs-down
+ \-1
+
+
+ .note-body
+ .note-text
+ = preserve do
+ = markdown(note.note)
+
+ .note-edit-form
+ = form_for note, url: project_note_path(@project, note), method: :put, remote: true do |f|
+ = f.text_area :note, class: 'note_text js-note-text js-gfm-input turn-on'
+
+ .form-actions
+ = f.submit 'Save changes', class: "btn btn-primary btn-save"
+
+ .note-form-option
+ %a.choose-btn.btn.btn-small.js-choose-note-attachment-button
+ %i.icon-paper-clip
+ %span Choose File ...
+ &nbsp;
+ %span.file_name.js-attachment-filename File name...
+ = f.file_field :attachment, class: "js-note-attachment-input hide"
+
+ = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel"
+
+
+ - if note.attachment.url
+ .note-attachment
+ - if note.attachment.image?
+ = link_to note.attachment.url, target: '_blank' do
+ = image_tag note.attachment.url, class: 'note-image-attach'
+ .attachment.pull-right
+ = link_to note.attachment.secure_url, target: "_blank" do
+ %i.icon-paper-clip
+ = note.attachment_identifier
+ = link_to delete_attachment_project_note_path(@project, note),
+ title: "Delete this attachment", method: :delete, remote: true, confirm: 'Are you sure you want to remove the attachment?', class: "danger js-note-attachment-delete" do
+ %i.icon-trash.cred
+ .clear
diff --git a/app/views/notes/_notes.html.haml b/app/views/projects/notes/_notes.html.haml
index 4904249aeff..ac8901fe704 100644
--- a/app/views/notes/_notes.html.haml
+++ b/app/views/projects/notes/_notes.html.haml
@@ -8,4 +8,4 @@
- else
- @notes.each do |note|
- next unless note.author
- = render 'note', note: note
+ = render note
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
new file mode 100644
index 00000000000..ac28c7432ef
--- /dev/null
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -0,0 +1,9 @@
+%ul#notes-list.notes
+.js-notes-busy
+
+.js-main-target-form
+- if can? current_user, :write_note, @project
+ = render "projects/notes/form"
+
+:javascript
+ NoteList.init("#{@target_id}", "#{@target_type}", "#{project_notes_path(@project)}");
diff --git a/app/views/notes/create.js.haml b/app/views/projects/notes/create.js.haml
index c573d406b73..c113b3482ec 100644
--- a/app/views/notes/create.js.haml
+++ b/app/views/projects/notes/create.js.haml
@@ -1,21 +1,18 @@
- if @note.valid?
- var noteHtml = "#{escape_javascript(render "notes/note", note: @note)}";
+ var noteHtml = "#{escape_javascript(render @note)}";
- if note_for_main_target?(@note)
- - if @note.for_wall?
- NoteList.appendNewWallNote(#{@note.id}, noteHtml);
- - else
- NoteList.appendNewNote(#{@note.id}, noteHtml);
+ NoteList.appendNewNote(#{@note.id}, noteHtml);
- else
:plain
- var firstDiscussionNoteHtml = "#{escape_javascript(render "notes/diff_notes_with_reply", notes: [@note])}";
+ var firstDiscussionNoteHtml = "#{escape_javascript(render "projects/notes/diff_notes_with_reply", notes: [@note])}";
NoteList.appendNewDiscussionNote("#{@note.discussion_id}",
firstDiscussionNoteHtml,
noteHtml);
- else
- var errorsHtml = "#{escape_javascript(render 'notes/form_errors', note: @note)}";
+ var errorsHtml = "#{escape_javascript(render 'projects/notes/form_errors', note: @note)}";
- if note_for_main_target?(@note)
NoteList.errorsOnForm(errorsHtml);
- else
- NoteList.errorsOnForm(errorsHtml, "#{@note.discussion_id}"); \ No newline at end of file
+ NoteList.errorsOnForm(errorsHtml, "#{@note.discussion_id}");
diff --git a/app/views/projects/notes/index.js.haml b/app/views/projects/notes/index.js.haml
new file mode 100644
index 00000000000..6c4ed203497
--- /dev/null
+++ b/app/views/projects/notes/index.js.haml
@@ -0,0 +1,4 @@
+- unless @notes.blank?
+ var notesHtml = "#{escape_javascript(render 'projects/notes/notes')}";
+ - new_note_ids = @notes.map(&:id)
+ NoteList.setContent(#{new_note_ids}, notesHtml);
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
new file mode 100644
index 00000000000..8930ec4b30a
--- /dev/null
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -0,0 +1,53 @@
+= render "projects/commits/head"
+.row
+ .span3
+ = render "projects/branches/filter"
+ .span9
+ .alert.alert-info
+ %p Protected branches designed to prevent push for all except #{link_to "masters", help_permissions_path, class: "vlink"}.
+ %p This ability allows:
+ %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"}
+
+ - if can? current_user, :admin_project, @project
+ = form_for [@project, @protected_branch] do |f|
+ -if @protected_branch.errors.any?
+ .alert.alert-error
+ %ul
+ - @protected_branch.errors.full_messages.each do |msg|
+ %li= msg
+
+ .entry.clearfix
+ = f.label :name, "Branch"
+ .span3
+ = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "chosen span3"})
+ &nbsp;
+ = f.submit 'Protect', class: "btn-create btn"
+ - unless @branches.empty?
+ %h5 Already Protected:
+ %ul.bordered-list.protected-branches-list
+ - @branches.each do |branch|
+ %li
+ %h4
+ = link_to project_commits_path(@project, branch.name) do
+ %strong= branch.name
+ - if @project.root_ref?(branch.name)
+ %span.label.label-info default
+ %span.label.label-success
+ %i.icon-lock
+ .pull-right
+ - if can? current_user, :admin_project, @project
+ = link_to 'Unprotect', [@project, branch], confirm: 'Branch will be writable for developers. Are you sure?', method: :delete, class: "btn btn-remove btn-small"
+
+ - if commit = branch.commit
+ = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
+ = commit.short_id
+ %span.light
+ = gfm escape_once(truncate(commit.title, length: 40))
+ %span
+ = time_ago_in_words(commit.committed_date)
+ ago
+ - else
+ (branch was removed from repository)
diff --git a/app/views/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml
index 23a6dae7810..213c54f5f75 100644
--- a/app/views/refs/logs_tree.js.haml
+++ b/app/views/projects/refs/logs_tree.js.haml
@@ -1,8 +1,9 @@
-- @logs.each do |content_data|
+- @logs.each do |content_data|
- file_name = content_data[:file_name]
- commit = content_data[:commit]
+ - next unless commit
:plain
var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}");
row.find("td.tree_time_ago").html('#{escape_javascript time_ago_in_words(commit.committed_date)} ago');
- row.find("td.tree_commit").html('#{escape_javascript render("tree/tree_commit_column", commit: commit)}');
+ row.find("td.tree_commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}');
diff --git a/app/views/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index eaf15ca77d6..faa3ed1746c 100644
--- a/app/views/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -1,5 +1,4 @@
- commit = update
-- commit = CommitDecorator.new(commit)
%tr
%td
= link_to project_commits_path(@project, commit.head.name) do
@@ -12,7 +11,7 @@
%div
= link_to project_commits_path(@project, commit.id) do
%code= commit.short_id
- = image_tag gravatar_icon(commit.author_email), class: "", width: 16
+ = image_tag gravatar_icon(commit.author_email), class: "", width: 16, alt: ''
= gfm escape_once(truncate(commit.title, length: 40))
%td
%span.pull-right.cgray
diff --git a/app/views/repositories/stats.html.haml b/app/views/projects/repositories/stats.html.haml
index dde35ea38aa..454296e82fd 100644
--- a/app/views/repositories/stats.html.haml
+++ b/app/views/projects/repositories/stats.html.haml
@@ -1,8 +1,8 @@
-= render "commits/head"
+= render "projects/commits/head"
.row
- .span5
- %h4
- Stats:
+ .span6
+ %div#activity-chart.chart
+ %hr
%p
%b Total commits:
%span= @stats.commits_count
@@ -13,14 +13,13 @@
%b Authors:
%span= @stats.authors_count
- %br
- %div#activity-chart
- .span7
+
+ .span6
%h4 Top 50 Committers:
%ol.styled
- @stats.authors[0...50].each do |author|
%li
- = image_tag gravatar_icon(author.email, 16), class: 'avatar s16'
+ = image_tag gravatar_icon(author.email, 16), class: 'avatar s16', alt: ''
= author.name
%small.light= author.email
.pull-right
@@ -28,14 +27,7 @@
:javascript
- $(function(){
- var labels = [#{@graph.labels.to_json}];
- var commits = [#{@graph.commits.join(', ')}];
- var r = Raphael('activity-chart');
- r.text(160, 10, "Commit activity for last #{@graph.weeks} weeks").attr({ font: "13px sans-serif" });
- r.barchart(
- 10, 10, 400, 160,
- [commits],
- {colors:["#456"]}
- ).label(labels, true);
- })
+ var labels = [#{@graph.labels.to_json}];
+ var commits = [#{@graph.commits.join(', ')}];
+ var title = "Commit activity for last #{@graph.weeks} weeks";
+ Chart.init(labels, commits, title);
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
new file mode 100644
index 00000000000..a099193cb6e
--- /dev/null
+++ b/app/views/projects/services/_form.html.haml
@@ -0,0 +1,48 @@
+%h3.page-title
+ - if @service.activated?
+ %span.cgreen
+ %i.icon-circle
+ - else
+ %span.cgray
+ %i.icon-circle-blank
+ = @service.title
+
+%p= @service.description
+
+.back-link
+ = link_to project_services_path(@project) do
+ &larr; to services
+
+%hr
+
+= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put) do |f|
+ - if @service.errors.any?
+ .alert.alert-error
+ %ul
+ - @service.errors.full_messages.each do |msg|
+ %li= msg
+
+
+ .control-group
+ = f.label :active, "Active", class: "control-label"
+ .controls
+ = f.check_box :active
+
+ - @service.fields.each do |field|
+ - name = field[:name]
+ - type = field[:type]
+ - placeholder = field[:placeholder]
+
+ .control-group
+ = f.label name, class: "control-label"
+ .controls
+ - if type == 'text'
+ = f.text_field name, class: "input-xlarge", placeholder: placeholder
+ - elsif type == 'checkbox'
+ = f.check_box name
+
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-save'
+ &nbsp;
+ - if @service.valid? && @service.activated? && @service.can_test?
+ = link_to 'Test settings', test_project_service_path(@project, @service.to_param), class: 'btn btn-small'
diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml
new file mode 100644
index 00000000000..bcc5832792f
--- /dev/null
+++ b/app/views/projects/services/edit.html.haml
@@ -0,0 +1 @@
+= render 'form'
diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml
new file mode 100644
index 00000000000..82b85a18acd
--- /dev/null
+++ b/app/views/projects/services/index.html.haml
@@ -0,0 +1,17 @@
+%h3.page-title Services
+%p.light Services allow you to integrate GitLab with other applications
+%hr
+
+%ul.bordered-list
+ - @services.each do |service|
+ %li
+ %h4
+ - if service.activated?
+ %span.cgreen
+ %i.icon-circle
+ - else
+ %span.cgray
+ %i.icon-circle-blank
+ = link_to edit_project_service_path(@project, service.to_param) do
+ = service.title
+ %p= service.description
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 2c4f55eb646..06ca5169dff 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,8 +1,47 @@
-= render "project_head"
= render 'clone_panel'
-= render "events/event_last_push", event: @last_push
-.content_list= render @events
-.loading.hide
-:javascript
- $(function(){ Pager.init(20); });
+.row
+ .span9
+ = render "events/event_last_push", event: @last_push
+ = render 'shared/event_filter'
+ .content_list
+ .loading.hide
+ .span3
+ .light-well
+ %h3.page-title
+ = @project.name
+ - if @project.description.present?
+ %p.light= @project.description
+
+ %hr
+ %p
+ %p
+ %span.light Repo size is
+ #{@project.repository.size} MB
+ %p
+ %span.light Created at
+ #{@project.created_at.stamp('Aug 22, 2013')}
+ %p
+ %span.light Owned by
+ - if @project.group
+ #{link_to @project.group.name, @project.group} Group
+ - else
+ #{link_to @project.owner_name, @project.owner}
+ - if @project.forked_from_project
+ %p
+ %i.icon-code-fork
+ Forked from:
+ = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
+
+ %hr
+ %p
+ = link_to pluralize(@repository.round_commit_count, 'commit'), project_commits_path(@project, @ref || @repository.root_ref)
+ %p
+ = link_to pluralize(@repository.branch_names.count, 'branch'), project_branches_path(@project)
+ %p
+ = link_to pluralize(@repository.tag_names.count, 'tag'), project_tags_path(@project)
+
+ - if @project.gitlab_ci?
+ %hr
+ = link_to @project.gitlab_ci_service.builds_path do
+ = image_tag @project.gitlab_ci_service.status_img_path, alt: "build status"
diff --git a/app/views/projects/snippets/_blob.html.haml b/app/views/projects/snippets/_blob.html.haml
new file mode 100644
index 00000000000..f14a2bd4ec0
--- /dev/null
+++ b/app/views/projects/snippets/_blob.html.haml
@@ -0,0 +1,15 @@
+.file-holder
+ .file-title
+ %i.icon-file
+ %strong= @snippet.file_name
+ %span.options
+ .btn-group.tree-btn-group.pull-right
+ - if can?(current_user, :admin_project_snippet, @project) || @snippet.author == current_user
+ = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-tiny", title: 'Edit Snippet'
+ = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank"
+ .file-content.code
+ - unless @snippet.content.empty?
+ %div{class: user_color_scheme_class}
+ = raw @snippet.colorize(formatter: :gitlab)
+ - else
+ %p.nothing_here_message Empty file
diff --git a/app/views/projects/snippets/_form.html.haml b/app/views/projects/snippets/_form.html.haml
new file mode 100644
index 00000000000..d414ee2d1ec
--- /dev/null
+++ b/app/views/projects/snippets/_form.html.haml
@@ -0,0 +1,44 @@
+%h3.page-title
+ = @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}"
+%hr
+.snippet-form-holder
+ = form_for [@project, @snippet], as: :project_snippet, url: url do |f|
+ -if @snippet.errors.any?
+ .alert.alert-error
+ %ul
+ - @snippet.errors.full_messages.each do |msg|
+ %li= msg
+
+ .control-group
+ = f.label :title
+ .controls= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true
+ .control-group
+ = f.label "Lifetime"
+ .controls= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'}
+ .control-group
+ .file-editor
+ = f.label :file_name, "File"
+ .controls
+ .file-holder.snippet
+ .file-title
+ = f.text_field :file_name, placeholder: "example.rb", class: 'snippet-file-name', required: true
+ .file-content.code
+ %pre#editor= @snippet.content
+ = f.hidden_field :content, class: 'snippet-file-content'
+
+ .form-actions
+ - if @snippet.new_record?
+ = f.submit 'Create snippet', class: "btn-create btn"
+ - else
+ = f.submit 'Save', class: "btn-save btn"
+ = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel"
+
+ - unless @snippet.new_record?
+ = link_to 'Remove snippet', project_snippet_path(@project, @snippet), confirm: 'Are you sure?', method: :delete, class: "btn pull-right btn-remove delete-snippet prepend-left-10", id: "destroy_snippet_#{@snippet.id}"
+
+:javascript
+ var editor = ace.edit("editor");
+ $(".snippet-form-holder form").submit(function(){
+ $(".snippet-file-content").val(editor.getValue());
+ });
+
diff --git a/app/views/projects/snippets/_snippet.html.haml b/app/views/projects/snippets/_snippet.html.haml
new file mode 100644
index 00000000000..fc1c0893b08
--- /dev/null
+++ b/app/views/projects/snippets/_snippet.html.haml
@@ -0,0 +1,21 @@
+%li
+ %h4.snippet-title
+ = link_to reliable_snippet_path(snippet) do
+ = truncate(snippet.title, length: 60)
+ %span.cgray.monospace.tiny.pull-right
+ = snippet.file_name
+
+ %small.pull-right.cgray
+ Expires:
+ - if snippet.expires_at
+ = snippet.expires_at.to_date.to_s(:short)
+ - else
+ Never
+
+ .snippet-info
+ = "##{snippet.id}"
+ %span
+ by
+ = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16"
+ = snippet.author_name
+ %span.light #{time_ago_in_words(snippet.created_at)} ago
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
new file mode 100644
index 00000000000..e28b7d4937e
--- /dev/null
+++ b/app/views/projects/snippets/edit.html.haml
@@ -0,0 +1 @@
+= render "projects/snippets/form", url: project_snippet_path(@project, @snippet)
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
new file mode 100644
index 00000000000..c40f63d05b3
--- /dev/null
+++ b/app/views/projects/snippets/index.html.haml
@@ -0,0 +1,15 @@
+%h3.page-title
+ Snippets
+ - if can? current_user, :write_project_snippet, @project
+ = link_to new_project_snippet_path(@project), class: "btn btn-new pull-right", title: "New Snippet" do
+ Add new snippet
+
+%p.light
+ Share code pastes with others out of git repository
+
+%hr
+%ul.bordered-list
+ = render partial: "projects/snippets/snippet", collection: @snippets
+ - if @snippets.empty?
+ %li
+ %h3.nothing_here_message Nothing here.
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
new file mode 100644
index 00000000000..460af34f676
--- /dev/null
+++ b/app/views/projects/snippets/new.html.haml
@@ -0,0 +1 @@
+= render "projects/snippets/form", url: project_snippets_path(@project, @snippet)
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
new file mode 100644
index 00000000000..4a07ebf7fd9
--- /dev/null
+++ b/app/views/projects/snippets/show.html.haml
@@ -0,0 +1,11 @@
+%h3.page-title
+ = @snippet.title
+
+ %small.pull-right
+ = "##{@snippet.id}"
+ %span.light
+ by
+ = image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
+ = @snippet.author_name
+%div= render 'projects/snippets/blob'
+%div#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
new file mode 100644
index 00000000000..5361517b2fc
--- /dev/null
+++ b/app/views/projects/tags/index.html.haml
@@ -0,0 +1,52 @@
+= render "projects/commits/head"
+
+- if can? current_user, :push_code, @project
+ .pull-right
+ = link_to new_project_tag_path(@project), class: 'btn btn-create new-tag-btn' do
+ %i.icon-add-sign
+ New tag
+
+%p
+ Tags give ability to mark specific points in history as being important
+%hr
+
+- unless @tags.empty?
+ %ul.bordered-list
+ - @tags.each do |tag|
+ - commit = Commit.new(Gitlab::Git::Commit.new(tag.commit))
+ %li
+ %h4
+ = link_to project_commits_path(@project, tag.name), class: "" do
+ %i.icon-tag
+ = tag.name
+ %small
+ = truncate(tag.message || '', length: 70)
+ .pull-right
+ %small.cdark
+ %i.icon-calendar
+ = time_ago_in_words(commit.committed_date)
+ ago
+ %p.prepend-left-20
+ = link_to commit.short_id(8), project_commit_path(@project, commit), class: "monospace"
+ &ndash;
+ = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "cdark"
+
+ %span.pull-right
+ - if can? current_user, :download_code, @project
+ = link_to archive_project_repository_path(@project, ref: tag.name), class: 'btn grouped btn-small' do
+ %i.icon-download-alt
+ Download
+ - if can?(current_user, :admin_project, @project)
+ = link_to project_tag_path(@project, tag.name), class: 'btn btn-small remove-row', method: :delete, confirm: 'Removed tag cannot be restored. Are you sure?', remote: true do
+ %i.icon-trash
+
+ = paginate @tags, theme: 'gitlab'
+
+- else
+ %h3.nothing_here_message
+ Repository has no tags yet.
+ %br
+ %small
+ Use git tag command to add a new one:
+ %br
+ %span.monospace git tag -a v1.4 -m 'version 1.4'
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
new file mode 100644
index 00000000000..67030785e06
--- /dev/null
+++ b/app/views/projects/tags/new.html.haml
@@ -0,0 +1,24 @@
+%h3.page-title
+ %i.icon-code-fork
+ New tag
+= form_tag project_tags_path, method: :post do
+ .control-group
+ = label_tag :tag_name, 'Name for new tag', class: 'control-label'
+ .controls
+ = text_field_tag :tag_name, nil, placeholder: 'v3.0.1', required: true, tabindex: 1
+ .control-group
+ = label_tag :ref, 'Create from', class: 'control-label'
+ .controls
+ = text_field_tag :ref, nil, placeholder: 'master', required: true, tabindex: 2
+ .light Branch name or commit SHA
+ .form-actions
+ = submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3
+ = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
+
+:javascript
+ var availableTags = #{@project.repository.ref_names.to_json};
+
+ $("#ref").autocomplete({
+ source: availableTags,
+ minLength: 1
+ });
diff --git a/app/views/projects/team_members/_form.html.haml b/app/views/projects/team_members/_form.html.haml
new file mode 100644
index 00000000000..5214a54e909
--- /dev/null
+++ b/app/views/projects/team_members/_form.html.haml
@@ -0,0 +1,24 @@
+%h3.page-title
+ = "New project member(s)"
+
+= form_for @user_project_relation, as: :team_member, url: project_team_members_path(@project) do |f|
+ -if @user_project_relation.errors.any?
+ .alert.alert-error
+ %ul
+ - @user_project_relation.errors.full_messages.each do |msg|
+ %li= msg
+
+ %p 1. Choose people you want in the project
+ .control-group
+ = f.label :user_ids, "People"
+ .controls
+ = users_select_tag(:user_ids, multiple: true)
+
+ %p 2. Set access level for them
+ .control-group
+ = f.label :project_access, "Project Access"
+ .controls= select_tag :project_access, options_for_select(Gitlab::Access.options, @user_project_relation.project_access), class: "project-access-select chosen"
+
+ .form-actions
+ = f.submit 'Add users', class: "btn btn-create"
+ = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
diff --git a/app/views/projects/team_members/_group_members.html.haml b/app/views/projects/team_members/_group_members.html.haml
new file mode 100644
index 00000000000..68f08006854
--- /dev/null
+++ b/app/views/projects/team_members/_group_members.html.haml
@@ -0,0 +1,10 @@
+.ui-box
+ .title
+ %strong #{@group.name}
+ group members (#{@group.users_groups.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|
+ = render 'users_groups/users_group', member: member, show_controls: false
diff --git a/app/views/projects/team_members/_team.html.haml b/app/views/projects/team_members/_team.html.haml
new file mode 100644
index 00000000000..2daf6847665
--- /dev/null
+++ b/app/views/projects/team_members/_team.html.haml
@@ -0,0 +1,9 @@
+.team-table
+ - can_admin_project = (can? current_user, :admin_project, @project)
+ .ui-box
+ .title
+ %strong #{@project.name}
+ project members (#{members.count})
+ %ul.well-list
+ - members.each do |team_member|
+ = render 'team_member', member: team_member, current_user_can_admin_project: can_admin_project
diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml
new file mode 100644
index 00000000000..916cf2e7a87
--- /dev/null
+++ b/app/views/projects/team_members/_team_member.html.haml
@@ -0,0 +1,17 @@
+- user = member.user
+%li{id: dom_id(user), class: "team_member_row access-#{member.human_access.downcase}"}
+ .pull-right
+ - if current_user_can_admin_project
+ - unless @project.personal? && user == current_user
+ .pull-left
+ = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f|
+ = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit"
+ &nbsp;
+ = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do
+ %i.icon-minus.icon-white
+ = image_tag gravatar_icon(user.email, 32), class: "avatar s32"
+ %p
+ %strong= user.name
+ %span.cgray= user.username
+
+
diff --git a/app/views/projects/team_members/import.html.haml b/app/views/projects/team_members/import.html.haml
new file mode 100644
index 00000000000..1d98b986210
--- /dev/null
+++ b/app/views/projects/team_members/import.html.haml
@@ -0,0 +1,14 @@
+%h3.page-title
+ = "Import members from another project"
+%p.light
+ Only project members will be imported. Group members will be skipped.
+%hr
+= form_tag apply_import_project_team_members_path(@project), method: 'post' do
+ .padded
+ = label_tag :source_project_id, "Project"
+ .controls= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "chosen xxlarge", required: true)
+
+ .form-actions
+ = submit_tag 'Import project members', class: "btn btn-create"
+ = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
+
diff --git a/app/views/projects/team_members/index.html.haml b/app/views/projects/team_members/index.html.haml
new file mode 100644
index 00000000000..acbe82919f1
--- /dev/null
+++ b/app/views/projects/team_members/index.html.haml
@@ -0,0 +1,16 @@
+%h3.page-title
+ Users with access to this project
+
+ - 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
+ New project member
+ = link_to import_project_team_members_path(@project), class: "btn grouped", title: "Import members from another project" do
+ Import members
+
+%p.light
+ Read more about project permissions
+ %strong= link_to "here", help_permissions_path, class: "vlink"
+= render "team", members: @users_projects
+- if @group
+ = render "group_members"
diff --git a/app/views/projects/team_members/new.html.haml b/app/views/projects/team_members/new.html.haml
new file mode 100644
index 00000000000..b1bc3ba0eba
--- /dev/null
+++ b/app/views/projects/team_members/new.html.haml
@@ -0,0 +1 @@
+= render "form"
diff --git a/app/views/team_members/update.js.haml b/app/views/projects/team_members/update.js.haml
index c68fe9574a2..c68fe9574a2 100644
--- a/app/views/team_members/update.js.haml
+++ b/app/views/projects/team_members/update.js.haml
diff --git a/app/views/projects/teams/available.html.haml b/app/views/projects/teams/available.html.haml
deleted file mode 100644
index da7823638b9..00000000000
--- a/app/views/projects/teams/available.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-= render "projects/project_head"
-
-%h3.page_title
- = "Assign project to team of users"
-%hr
-%p.slead
- Read more about assign to team of users #{link_to "here", '#', class: 'vlink'}.
-= form_tag assign_project_teams_path(@project), method: 'post' do
- %p.slead Choose Team of users you want to assign:
- .padded
- = label_tag :team_id, "Team"
- .input= select_tag(:team_id, options_from_collection_for_select(@teams, :id, :name), prompt: "Select team", class: "chosen xxlarge", required: true)
- %p.slead Choose greatest user acces in team you want to assign:
- .padded
- = label_tag :team_ids, "Permission"
- .input= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles), {class: "project-access-select chosen span3" }
-
-
- .actions
- = submit_tag 'Assign', class: "btn btn-create"
- = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
-
diff --git a/app/views/projects/transfer.js.haml b/app/views/projects/transfer.js.haml
new file mode 100644
index 00000000000..10b0de98c04
--- /dev/null
+++ b/app/views/projects/transfer.js.haml
@@ -0,0 +1,7 @@
+- if @project.errors[:namespace_id].present?
+ :plain
+ $("#tab-transfer .errors-holder").replaceWith(errorMessage('#{escape_javascript(@project.errors[:namespace_id].first)}'));
+ $("#tab-transfer .form-actions input").removeAttr('disabled').removeClass('disabled');
+- else
+ :plain
+ location.href = "#{edit_project_path(@project)}";
diff --git a/app/views/projects/tree.js.haml b/app/views/projects/tree.js.haml
deleted file mode 100644
index ba5d53c16a2..00000000000
--- a/app/views/projects/tree.js.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-:plain
- $("#tree-holder table").hide("slide", { direction: "left" }, 150, function(){
- $("#tree-holder").html("#{escape_javascript(render(partial: "tree", locals: {repo: @repo, commit: @commit, tree: @tree}))}");
- $("#tree-holder table").show("slide", { direction: "right" }, 150);
- });
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
new file mode 100644
index 00000000000..b179ad7d245
--- /dev/null
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -0,0 +1,9 @@
+%tr{ class: "tree-item #{tree_hex_class(blob_item)}" }
+ %td.tree-item-file-name
+ = tree_icon(type)
+ %span= link_to truncate(blob_item.name, length: 40), project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name))
+ %td.tree_time_ago.cgray
+ %span.log_loading.hide
+ Loading commit data...
+ = image_tag "ajax_loader_tree.gif", width: 14
+ %td.tree_commit{ colspan: 2 }
diff --git a/app/views/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index e9bb112745b..98bacb49562 100644
--- a/app/views/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,8 +1,8 @@
-.file_holder#README
- .file_title
+.file-holder#README
+ .file-title
%i.icon-file
= readme.name
- .file_content.wiki
+ .file-content.wiki
- if gitlab_markdown?(readme.name)
= preserve do
= markdown(readme.data)
diff --git a/app/views/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml
index 092a024afbc..26aad16e2c0 100644
--- a/app/views/tree/_submodule_item.html.haml
+++ b/app/views/projects/tree/_submodule_item.html.haml
@@ -4,7 +4,7 @@
%tr{ class: "tree-item", url: url }
%td.tree-item-file-name
= image_tag "submodule.png"
- %strong= truncate(name, length: 40)
+ %span= truncate(name, length: 40)
%td
%code= submodule_item.id[0..10]
%td{ colspan: 2 }
diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml
new file mode 100644
index 00000000000..ae5f30c0004
--- /dev/null
+++ b/app/views/projects/tree/_tree.html.haml
@@ -0,0 +1,51 @@
+%ul.breadcrumb
+ %li
+ %i.icon-angle-right
+ = link_to project_tree_path(@project, @ref) do
+ = @project.path
+ - tree_breadcrumbs(tree, 6) do |title, path|
+ \/
+ %li
+ - if path
+ = link_to truncate(title, length: 40), project_tree_path(@project, path)
+ - else
+ = link_to title, '#'
+
+%div#tree-content-holder.tree-content-holder
+ %table#tree-slider{class: "table_#{@hex_path} tree-table" }
+ %thead
+ %tr
+ %th Name
+ %th Last Update
+ %th
+ Last Commit
+ &nbsp;
+ %i.icon-angle-right
+ &nbsp;
+ %small.light
+ = link_to @commit.short_id, project_commit_path(@project, @commit)
+ &ndash;
+ = truncate(@commit.title, length: 50)
+ %th= link_to "history", project_commits_path(@project, @id), class: "pull-right"
+
+ - if tree.up_dir?
+ %tr.tree-item
+ %td.tree-item-file-name
+ = image_tag "file_empty.png", size: '16x16'
+ = link_to "..", project_tree_path(@project, up_dir_path(tree))
+ %td
+ %td
+ %td
+
+ = render_tree(tree)
+
+ - if tree.readme
+ = render "projects/tree/readme", readme: tree.readme
+
+%div.tree_progress
+
+:javascript
+ // Load last commit log for each file in tree
+ $('#tree-slider').waitForImages(function() {
+ ajaxGet('#{@logs_path}');
+ });
diff --git a/app/views/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index 9d02132b0f4..7ae2582c130 100644
--- a/app/views/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,2 +1,2 @@
-%span.tree_author= commit.author_link avatar: true
+%span.tree_author= commit_author_link(commit, avatar: true)
= link_to_gfm truncate(commit.title, length: 80), project_commit_path(@project, commit.id), class: "tree-commit-link"
diff --git a/app/views/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml
index 0a76d5c21b6..f8856afc866 100644
--- a/app/views/tree/_tree_item.html.haml
+++ b/app/views/projects/tree/_tree_item.html.haml
@@ -1,7 +1,7 @@
%tr{ class: "tree-item #{tree_hex_class(tree_item)}" }
%td.tree-item-file-name
= tree_icon(type)
- %strong= link_to truncate(tree_item.name, length: 40), project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name))
+ %span= link_to truncate(tree_item.name, length: 40), project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name))
%td.tree_time_ago.cgray
%span.log_loading.hide
Loading commit data...
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
new file mode 100644
index 00000000000..0f7692aba7f
--- /dev/null
+++ b/app/views/projects/tree/show.html.haml
@@ -0,0 +1,4 @@
+%div.tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'tree', path: @path
+%div#tree-holder.tree-holder
+ = render "tree", tree: @tree
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index f44ed529182..cbb21f2b9fb 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -3,6 +3,7 @@
location.href = "#{edit_project_path(@project)}";
- else
:plain
- $('.project_edit_holder').show();
- $(".edit_project").replaceWith("#{escape_javascript(render('form'))}");
+ $(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.save-project-loader').hide();
+ $('.project-edit-container').show();
+ $('.project-edit-content .btn-save').enableButton();
diff --git a/app/views/projects/update_failed.js.haml b/app/views/projects/update_failed.js.haml
deleted file mode 100644
index a3ac5f4088f..00000000000
--- a/app/views/projects/update_failed.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
- $(".save-project-loader").replaceWith(errorMessage('#{escape_javascript(@error.message)}'));
diff --git a/app/views/projects/wall.html.haml b/app/views/projects/wall.html.haml
deleted file mode 100644
index 82b565def43..00000000000
--- a/app/views/projects/wall.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-%div.wall_page
- = render "notes/reversed_notes_with_form"
diff --git a/app/views/projects/walls/show.html.haml b/app/views/projects/walls/show.html.haml
new file mode 100644
index 00000000000..88aecee0815
--- /dev/null
+++ b/app/views/projects/walls/show.html.haml
@@ -0,0 +1,23 @@
+%div.wall-page
+ %ul.notes
+
+ - if can? current_user, :write_note, @project
+ .note-form-holder
+ = form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note wall-note-form" } do |f|
+ = note_target_fields
+ .note_text_and_preview
+ = 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"
+
+ .note-form-option
+ %a.choose-btn.btn.btn-small.js-choose-note-attachment-button
+ %i.icon-paper-clip
+ %span Choose File ...
+ &nbsp;
+ %span.file_name.js-attachment-filename File name...
+ = f.file_field :attachment, class: "js-note-attachment-input hide"
+
+ .hint.pull-right CTRL + Enter to send message
+ .clearfix
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
new file mode 100644
index 00000000000..16061c9dcbb
--- /dev/null
+++ b/app/views/projects/wikis/_form.html.haml
@@ -0,0 +1,39 @@
+= form_for [@project, @wiki] do |f|
+ -if @wiki.errors.any?
+ #error_explanation
+ %h2= "#{pluralize(@wiki.errors.count, "error")} prohibited this wiki from being saved:"
+ %ul
+ - @wiki.errors.full_messages.each do |msg|
+ %li= msg
+
+ .ui-box.ui-box-show
+ .ui-box-head
+ %h3.page-title
+ .edit-wiki-header
+ = @wiki.title.titleize
+ = f.hidden_field :title, value: @wiki.title
+ = f.select :format, options_for_select(GollumWiki::MARKUPS, {selected: @wiki.format}), {}, class: "pull-right input-medium"
+ = f.label :format, class: "pull-right", style: "padding-right: 20px;"
+ .ui-box-body
+ .controls
+ %span.cgray
+ Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
+ To link to a (new) page you can just type
+ %code [Link Title](page-slug)
+ \.
+
+ .ui-box-bottom
+ .control-group
+ = f.label :content
+ .controls= f.text_area :content, class: 'span8 js-gfm-input'
+ .ui-box-bottom
+ .control-group
+ = f.label :commit_message
+ .controls= f.text_field :message, class: 'span8'
+ .form-actions
+ - if @wiki && @wiki.persisted?
+ = f.submit 'Save changes', class: "btn-save btn"
+ = link_to "Cancel", project_wiki_path(@project, @wiki), class: "btn btn-cancel"
+ - else
+ = f.submit 'Create page', class: "btn-create btn"
+ = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel"
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
new file mode 100644
index 00000000000..1001bb10c4f
--- /dev/null
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -0,0 +1,8 @@
+%span.pull-right
+ - if (@wiki && @wiki.persisted?)
+ = link_to history_project_wiki_path(@project, @wiki), class: "btn grouped" do
+ Page History
+ - if can?(current_user, :write_wiki, @project)
+ = link_to edit_project_wiki_path(@project, @wiki), class: "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
new file mode 100644
index 00000000000..0a7e51e974c
--- /dev/null
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -0,0 +1,19 @@
+%ul.nav.nav-tabs
+ = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
+ = link_to 'Home', project_wiki_path(@project, :home)
+
+ = nav_link(path: 'wikis#pages') do
+ = link_to 'Pages', pages_project_wikis_path(@project)
+
+ = nav_link(path: 'wikis#git_access') do
+ = link_to git_access_project_wikis_path(@project) do
+ %i.icon-download-alt
+ Git Access
+
+ - if can?(current_user, :write_wiki, @project)
+ .pull-right
+ = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+ %i.icon-plus
+ New Page
+
+= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
new file mode 100644
index 00000000000..f64772b2001
--- /dev/null
+++ b/app/views/projects/wikis/_new.html.haml
@@ -0,0 +1,12 @@
+%div#modal-new-wiki.modal.hide
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3.page-title New Wiki Page
+ .modal-body
+ = label_tag :new_wiki_path do
+ %span Page slug
+ = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'input-xlarge', required: true, :'data-wikis-path' => project_wikis_path(@project)
+ %p.hint
+ Please don't use spaces and slashes
+ .modal-footer
+ = link_to 'Build', '#', class: 'build-new-wiki btn btn-create'
diff --git a/app/views/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 9e221aba47d..66be82777c9 100644
--- a/app/views/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,8 +1,10 @@
-%h3.page_title Editing page
-%hr
+= render 'nav'
+%h3.page-title
+ Editing page
+ = render 'main_links'
= render 'form'
.pull-right
- - if can? current_user, :admin_wiki, @project
+ - if @wiki.persisted? && can?(current_user, :admin_wiki, @project)
= link_to project_wiki_path(@project, @wiki), confirm: "Are you sure you want to delete this page?", method: :delete, class: "btn btn-small btn-remove" do
- Delete this page \ No newline at end of file
+ Delete this page
diff --git a/app/views/wikis/empty.html.haml b/app/views/projects/wikis/empty.html.haml
index 08b59f0328b..48058124f97 100644
--- a/app/views/wikis/empty.html.haml
+++ b/app/views/projects/wikis/empty.html.haml
@@ -1,4 +1,4 @@
-%h3.page_title Empty page
+%h3.page-title Empty page
%hr
.error_message
You are not allowed to create wiki pages
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
new file mode 100644
index 00000000000..dd01bb99041
--- /dev/null
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -0,0 +1,36 @@
+= render 'nav'
+%h3.page-title
+ Git access for
+ %strong= @gollum_wiki.path_with_namespace
+ = render 'main_links'
+
+.content
+ .project_clone_panel
+ .row
+ .span7
+ .form-horizontal
+ .input-prepend.project_clone_holder
+ %button{class: "btn active", :"data-clone" => @gollum_wiki.ssh_url_to_repo} SSH
+ %button{class: "btn", :"data-clone" => @gollum_wiki.http_url_to_repo}= Gitlab.config.gitlab.protocol.upcase
+ = text_field_tag :project_clone, @gollum_wiki.url_to_repo, class: "one_click_select input-xxlarge", readonly: true
+ .git-empty
+ %fieldset
+ %legend Install Gollum:
+ %pre.dark
+ :preserve
+ gem install gollum
+
+ %legend Clone Your Wiki:
+ %pre.dark
+ :preserve
+ git clone #{@gollum_wiki.ssh_url_to_repo}
+ cd #{@gollum_wiki.path}
+
+ %legend Start Gollum And Edit Locally:
+ %pre.dark
+ :preserve
+ gollum
+ == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin
+ >> Thin web server (v1.5.0 codename Knife)
+ >> Maximum connections set to 1024
+ >> Listening on 0.0.0.0:4567, CTRL+C to stop
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
new file mode 100644
index 00000000000..344a5e88e6e
--- /dev/null
+++ b/app/views/projects/wikis/history.html.haml
@@ -0,0 +1,30 @@
+= render 'nav'
+%h3.page-title
+ %span.light History for
+ = link_to @wiki.title.titleize, project_wiki_path(@project, @wiki)
+
+%table
+ %thead
+ %tr
+ %th Page version
+ %th Author
+ %th Commit Message
+ %th Last updated
+ %th Format
+ %tbody
+ - @wiki.versions.each do |version|
+ - commit = version
+ %tr
+ %td
+ = link_to project_wiki_path(@project, @wiki, version_id: commit.id) do
+ = commit.short_id
+ %td
+ = commit_author_link(commit, avatar: true, size: 24)
+ %td
+ = commit.title
+ %td
+ = time_ago_in_words(version.date)
+ ago
+ %td
+ %strong
+ = @wiki.page.wiki.page(@wiki.page.name, commit.id).try(:format)
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
new file mode 100644
index 00000000000..59d104a985b
--- /dev/null
+++ b/app/views/projects/wikis/pages.html.haml
@@ -0,0 +1,11 @@
+= render 'nav'
+%h3.page-title
+ All Pages
+%ul.bordered-list
+ - @wiki_pages.each do |wiki_page|
+ %li
+ %h4
+ = link_to wiki_page.title.titleize, project_wiki_path(@project, wiki_page)
+ %small (#{wiki_page.format})
+ .pull-right
+ %small Last edited #{time_ago_in_words(wiki_page.commit.created_at)} ago
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
new file mode 100644
index 00000000000..63e7f12b7fe
--- /dev/null
+++ b/app/views/projects/wikis/show.html.haml
@@ -0,0 +1,15 @@
+= render 'nav'
+%h3.page-title
+ = @wiki.title.titleize
+ = render 'main_links'
+- if @wiki.historical?
+ .warning_message
+ This is an old version of this page.
+ You can view the #{link_to "most recent version", project_wiki_path(@project, @wiki)} or browse the #{link_to "history", history_project_wiki_path(@project, @wiki)}.
+
+.file-holder
+ .file-content.wiki
+ = preserve do
+ = render_wiki_content(@wiki)
+
+%p.time Last edited by #{commit_author_link(@wiki.commit, avatar: true, size: 16)} #{time_ago_in_words @wiki.commit.created_at} ago
diff --git a/app/views/protected_branches/index.html.haml b/app/views/protected_branches/index.html.haml
deleted file mode 100644
index 15644de552f..00000000000
--- a/app/views/protected_branches/index.html.haml
+++ /dev/null
@@ -1,54 +0,0 @@
-= render "commits/head"
-.row
- .span3
- = render "repositories/filter"
- .span9
- .alert
- %p Protected branches designed to prevent push for all except #{link_to "masters", help_permissions_path, class: "vlink"}.
- %p This ability allows:
- %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: "vlink"}
-
- - if can? current_user, :admin_project, @project
- = form_for [@project, @protected_branch] do |f|
- -if @protected_branch.errors.any?
- .alert.alert-error
- %ul
- - @protected_branch.errors.full_messages.each do |msg|
- %li= msg
-
- .entry.clearfix
- = f.label :name, "Branch"
- .span3
- = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "chosen span3"})
- &nbsp;
- = f.submit 'Protect', class: "btn-primary btn"
-
- - unless @branches.empty?
- %table
- %thead
- %tr
- %th Name
- %th Last commit
- %th
- %tbody
- - @branches.each do |branch|
- %tr
- %td
- = link_to project_commits_path(@project, branch.name) do
- %strong= branch.name
- - if @project.root_ref?(branch.name)
- %span.label default
- %td
- - if branch.commit
- = link_to project_commit_path(@project, branch.commit.id) do
- = truncate branch.commit.id.to_s, length: 10
- = time_ago_in_words(branch.commit.committed_date)
- ago
- - else
- (branch was removed from repository)
- %td
- - if can? current_user, :admin_project, @project
- = link_to 'Unprotect', [@project, branch], confirm: 'Are you sure?', method: :delete, class: "btn btn-remove btn-small"
diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml
index 21e9d2e6029..21aee644579 100644
--- a/app/views/public/projects/index.html.haml
+++ b/app/views/public/projects/index.html.haml
@@ -1,16 +1,42 @@
-%h3.page_title
- Projects
- %small with read-only access
+.row
+ .span6
+ %h3.page-title
+ Projects (#{@projects.total_count})
+ .light
+ You can browse public projects in read-only mode until signed in.
+
+ .span6
+ .pull-right
+ = form_tag public_projects_path, method: :get, class: 'form-inline' do |f|
+ .search-holder
+ = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "span3 search-text-input", id: "projects_search"
+ = submit_tag 'Search', class: "btn btn-primary wide"
%hr
+.public-projects
+ %ul.bordered-list.top-list
+ - @projects.each do |project|
+ %li
+ %h4
+ = link_to project_path(project) do
+ = project.name_with_namespace
+ .pull-right
+ %pre.public-clone git clone #{project.http_url_to_repo}
-%ul.unstyled
- - @projects.each do |project|
- %li.clearfix
- %h5
- %i.icon-share
- = project.name_with_namespace
- .pull-right
- %pre.dark.tiny git clone #{project.http_url_to_repo}
+ - if project.description.present?
+ %p
+ = project.description
+ .repo-info
+ - unless project.empty_repo?
+ = link_to pluralize(project.repository.round_commit_count, 'commit'), project_commits_path(project, project.default_branch)
+ &middot;
+ = link_to pluralize(project.repository.branch_names.count, 'branch'), project_branches_path(project)
+ &middot;
+ = link_to pluralize(project.repository.tag_names.count, 'tag'), project_tags_path(project)
+ - else
+ %i.icon-warning-sign
+ Empty repository
+ - unless @projects.present?
+ %h3.nothing_here_message No public projects
-= paginate @projects, theme: "admin"
+ = paginate @projects, theme: "gitlab"
diff --git a/app/views/repositories/_branch.html.haml b/app/views/repositories/_branch.html.haml
deleted file mode 100644
index a6faa5fd633..00000000000
--- a/app/views/repositories/_branch.html.haml
+++ /dev/null
@@ -1,27 +0,0 @@
-- commit = Commit.new(branch.commit)
-- commit = CommitDecorator.decorate(commit)
-%tr
- %td
- = link_to project_commits_path(@project, branch.name) do
- - if @project.protected_branch? branch.name
- %i.icon-lock
- - else
- %i.icon-unlock
- %strong= truncate(branch.name, length: 60)
- - if branch.name == @repository.root_ref
- %span.label default
- %td
- = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
- = commit.short_id
- = image_tag gravatar_icon(commit.author_email), class: "avatar s16"
- %span.light
- = gfm escape_once(truncate(commit.title, length: 40))
- %span
- = time_ago_in_words(commit.committed_date)
- ago
- %td
- - if can? current_user, :download_code, @project
- = link_to archive_project_repository_path(@project, ref: branch.name) do
- %i.icon-download-alt
- Download
-
diff --git a/app/views/repositories/_filter.html.haml b/app/views/repositories/_filter.html.haml
deleted file mode 100644
index e718d48190a..00000000000
--- a/app/views/repositories/_filter.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-%ul.nav.nav-pills.nav-stacked
- = nav_link(path: 'repositories#show') do
- = link_to 'Recent', project_repository_path(@project)
- = nav_link(path: 'protected_branches#index') do
- = link_to project_protected_branches_path(@project) do
- Protected
- %i.icon-lock
- = nav_link(path: 'repositories#branches') do
- = link_to 'All branches', branches_project_repository_path(@project)
diff --git a/app/views/repositories/_head.html.haml b/app/views/repositories/_head.html.haml
deleted file mode 100644
index bc96f306eee..00000000000
--- a/app/views/repositories/_head.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= render "projects/project_head"
diff --git a/app/views/repositories/branches.html.haml b/app/views/repositories/branches.html.haml
deleted file mode 100644
index 14b5082e44e..00000000000
--- a/app/views/repositories/branches.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-= render "commits/head"
-.row
- .span3
- = render "filter"
- .span9
- - unless @branches.empty?
- %table
- %thead
- %tr
- %th Name
- %th Last commit
- %th
- %tbody
- - @branches.each do |branch|
- = render "repositories/branch", branch: branch
diff --git a/app/views/repositories/show.html.haml b/app/views/repositories/show.html.haml
deleted file mode 100644
index e58e16f8bf1..00000000000
--- a/app/views/repositories/show.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-= render "commits/head"
-.row
- .span3
- = render "filter"
- .span9
- %table
- %thead
- %tr
- %th Name
- %th Last commit
- %th
- - @activities.each do |update|
- = render "repositories/branch", branch: update.head
-
diff --git a/app/views/repositories/tags.html.haml b/app/views/repositories/tags.html.haml
deleted file mode 100644
index d4b8bbe10d4..00000000000
--- a/app/views/repositories/tags.html.haml
+++ /dev/null
@@ -1,39 +0,0 @@
-= render "commits/head"
-- unless @tags.empty?
- %table
- %thead
- %tr
- %th Name
- %th Last commit
- %th
- - @tags.each do |tag|
- - commit = Commit.new(tag.commit)
- - commit = CommitDecorator.decorate(commit)
- %tr
- %td
- %strong
- = link_to project_commits_path(@project, tag.name), class: "" do
- %i.icon-tag
- = tag.name
- %small.light= truncate(tag.message || '', length: 70)
- %td
- = image_tag gravatar_icon(commit.author_email), class: "avatar s16"
- = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
- = commit.short_id
- %span.light
- = time_ago_in_words(commit.committed_date)
- ago
- %td
- - if can? current_user, :download_code, @project
- = link_to archive_project_repository_path(@project, ref: tag.name) do
- %i.icon-download-alt
- Download
-
-- else
- %h3.nothing_here_message
- Repository has no tags yet.
- %br
- %small
- Use git tag command to add a new one:
- %br
- %span.monospace git tag -a v1.4 -m 'version 1.4'
diff --git a/app/views/search/_blob.html.haml b/app/views/search/_blob.html.haml
new file mode 100644
index 00000000000..559fdd794fc
--- /dev/null
+++ b/app/views/search/_blob.html.haml
@@ -0,0 +1,10 @@
+%li
+ .file-holder
+ .file-title
+ = link_to project_blob_path(@project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do
+ %i.icon-file
+ %strong
+ = blob.filename
+ .file-content.code.term
+ %div{class: user_color_scheme_class}
+ = raw blob.colorize( formatter: :gitlab, options: { first_line_number: blob.startline } )
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index 3fe17dce857..f7a00b23480 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -1,24 +1,35 @@
-%fieldset
- %legend Groups:
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if params[:group_id].blank?)}
- = link_to search_path(group_id: nil, search: params[:search]) do
+.dropdown.inline
+ %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
+ %i.icon-tags
+ %span.light Group:
+ - if @group.present?
+ %strong= @group.name
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to search_path(group_id: nil) do
Any
- - current_user.authorized_groups.each do |group|
- %li{class: ("active" if params[:group_id] == group.id.to_s)}
+ - current_user.authorized_groups.sort_by(&:name).each do |group|
+ %li
= link_to search_path(group_id: group.id, search: params[:search]) do
= group.name
-%fieldset
- %legend Projects:
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if params[:project_id].blank?)}
- = link_to search_path(project_id: nil, search: params[:search]) do
+.dropdown.inline.prepend-left-10
+ %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
+ %i.icon-tags
+ %span.light Project:
+ - if @project.present?
+ %strong= @project.name_with_namespace
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to search_path(project_id: nil) do
Any
- - current_user.authorized_projects.each do |project|
- %li{class: ("active" if params[:project_id] == project.id.to_s)}
+ - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project|
+ %li
= link_to search_path(project_id: project.id, search: params[:search]) do
= project.name_with_namespace
-
-= hidden_field_tag :group_id, params[:group_id]
-= hidden_field_tag :project_id, params[:project_id]
diff --git a/app/views/search/_result.html.haml b/app/views/search/_result.html.haml
index bfa46075baa..5f7540d1b16 100644
--- a/app/views/search/_result.html.haml
+++ b/app/views/search/_result.html.haml
@@ -1,9 +1,19 @@
%fieldset
%legend
Search results
- %span.cgray (#{@projects.count + @merge_requests.count + @issues.count + @wiki_pages.count})
+ %span.cgray (#{@total_results})
+
+- if @project
+ %ul.nav.nav-pills
+ %li{class: ("active" if params[:search_code].present?)}
+ = link_to search_path(params.merge(search_code: true)) do
+ Repository Code
+ %li{class: ("active" if params[:search_code].blank?)}
+ = link_to search_path(params.merge(search_code: nil)) do
+ Everything else
+
.search_results
- %ul.well-list
+ %ul.bordered-list
- @projects.each do |project|
%li
project:
@@ -12,19 +22,28 @@
- @merge_requests.each do |merge_request|
%li
merge request:
- = link_to [merge_request.project, merge_request] do
- %span ##{merge_request.id}
+ = link_to [merge_request.target_project, merge_request] do
+ %span ##{merge_request.iid}
%strong.term
= truncate merge_request.title, length: 50
- %span.light (#{merge_request.project.name_with_namespace})
+ - if merge_request.for_fork?
+ %span.light (#{merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} &rarr; #{merge_request.target_project.name_with_namespace}:#{merge_request.target_branch})
+ - else
+ %span.light (#{merge_request.source_branch} &rarr; #{merge_request.target_branch})
+ - if merge_request.closed?
+ %span.label Closed
+
- @issues.each do |issue|
%li
issue:
= link_to [issue.project, issue] do
- %span ##{issue.id}
+ %span ##{issue.iid}
%strong.term
= truncate issue.title, length: 50
%span.light (#{issue.project.name_with_namespace})
+ - if issue.closed?
+ %span.label Closed
+
- @wiki_pages.each do |wiki_page|
%li
wiki:
@@ -33,8 +52,11 @@
= truncate wiki_page.title, length: 50
%span.light (#{wiki_page.project.name_with_namespace})
+ - @blobs.each do |blob|
+ = render 'blob', blob: blob
+
+ = paginate @blobs, theme: 'gitlab'
+
:javascript
- $(function() {
- $(".search_results .term").highlight("#{escape_javascript(params[:search])}");
- })
+ $(".search_results .term").highlight("#{escape_javascript(params[:search])}");
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 5914c22df6e..f1f65981b37 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -2,14 +2,15 @@
.search-holder
= label_tag :search do
%span Looking for
- .input
+ .controls
= search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search"
- = submit_tag 'Search', class: "btn btn-primary wide"
- .clearfix
- .row
- .span3
- = render 'filter', f: f
- .span9
- .results
- - if params[:search].present?
- = render 'search/result'
+ = hidden_field_tag :project_id, params[:project_id]
+ = hidden_field_tag :group_id, params[:group_id]
+ = hidden_field_tag :search_code, params[:search_code]
+ = submit_tag 'Search', class: "btn btn-create"
+ .prepend-top-10
+ = render 'filter', f: f
+
+ .results.prepend-top-10
+ - if params[:search].present?
+ = render 'search/result'
diff --git a/app/views/services/_gitlab_ci.html.haml b/app/views/services/_gitlab_ci.html.haml
deleted file mode 100644
index dfde643849e..00000000000
--- a/app/views/services/_gitlab_ci.html.haml
+++ /dev/null
@@ -1,46 +0,0 @@
-%h3.page_title
- GitLab CI
- %small Continuous integration server from GitLab
- .pull-right
- - if @service.active
- %small.cgreen Enabled
- - else
- %small.cgray Disabled
-
-
-
-.back_link
- = link_to project_services_path(@project) do
- &larr; to services
-
-%hr
-= form_for(@service, :as => :service, :url => project_service_path(@project, :gitlab_ci), :method => :put) do |f|
- - if @service.errors.any?
- .alert.alert-error
- %ul
- - @service.errors.full_messages.each do |msg|
- %li= msg
-
-
- .control-group
- = f.label :active, "Active", class: "control-label"
- .controls
- = f.check_box :active
-
- .control-group
- = f.label :project_url, "Project URL", class: "control-label"
- .controls
- = f.text_field :project_url, class: "input-xlarge", placeholder: "http://ci.gitlabhq.com/projects/3"
-
- .control-group
- = f.label :token, class: "control-label" do
- CI Project token
- .controls
- = f.text_field :token, class: "input-xlarge", placeholder: "GitLab CI project specific token"
-
-
- .form-actions
- = f.submit 'Save', class: 'btn btn-save'
- &nbsp;
- - if @service.valid? && @service.active
- = link_to 'Test settings', test_project_service_path(@project), class: 'btn btn-small'
diff --git a/app/views/services/edit.html.haml b/app/views/services/edit.html.haml
deleted file mode 100644
index d893847f1ae..00000000000
--- a/app/views/services/edit.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-= render "projects/project_head"
-= render 'gitlab_ci'
diff --git a/app/views/services/index.html.haml b/app/views/services/index.html.haml
deleted file mode 100644
index 27dbf502569..00000000000
--- a/app/views/services/index.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-= render "projects/project_head"
-%h3.page_title Services
-%br
-
-%ul.ui-box.well-list
- %li
- %h4.cgreen
- = link_to edit_project_service_path(@project, :gitlab_ci) do
- GitLab CI
- %small Continuous integration server from GitLab
- .pull-right
- - if @gitlab_ci_service.try(:active)
- %small.cgreen
- %i.icon-ok
- Enabled
- - else
- %small.cgray
- %i.icon-off
- Disabled
- %li.disabled
- %h4
- Jenkins CI
- %small An extendable open source continuous integration server
- .pull-right
- %small Not implemented yet
- %li.disabled
- %h4
- Campfire
- %small Web-based group chat tool
- .pull-right
- %small Not implemented yet
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 7b5de4a6274..5120902fa0e 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -1,5 +1,11 @@
-.input-prepend.project_clone_holder
+.input-prepend.input-append.project_clone_holder
%button{class: "btn active", :"data-clone" => @project.ssh_url_to_repo} SSH
- %button{class: "btn", :"data-clone" => @project.http_url_to_repo}= Gitlab.config.gitlab.protocol.upcase
-
- = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select input-xxlarge"
+ %button{class: "btn", :"data-clone" => @project.http_url_to_repo}= gitlab_config.protocol.upcase
+ = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span7", readonly: true
+ %span.add-on
+ - if @project.public
+ = public_icon
+ %span.cblue public
+ - else
+ = private_icon
+ %span.cgreen private
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
new file mode 100644
index 00000000000..ee0b57fbe5a
--- /dev/null
+++ b/app/views/shared/_event_filter.html.haml
@@ -0,0 +1,5 @@
+.event_filter
+ = event_filter_link EventFilter.push, 'Push events'
+ = event_filter_link EventFilter.merged, 'Merge events'
+ = event_filter_link EventFilter.comments, 'Comments'
+ = event_filter_link EventFilter.team, 'Team'
diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml
new file mode 100644
index 00000000000..2f051cea48b
--- /dev/null
+++ b/app/views/shared/_filter.html.haml
@@ -0,0 +1,29 @@
+= form_tag filter_path(entity), method: 'get' do
+ %fieldset
+ %ul.nav.nav-pills.nav-stacked
+ %li{class: ("active" if params[:status].blank?)}
+ = link_to filter_path(entity, status: nil) do
+ Open
+ %li{class: ("active" if params[:status] == 'closed')}
+ = link_to filter_path(entity, status: 'closed') do
+ Closed
+ %li{class: ("active" if params[:status] == 'all')}
+ = link_to filter_path(entity, status: 'all') do
+ All
+
+ %fieldset
+ %legend Projects
+ %ul.nav.nav-pills.nav-pills-small.nav-stacked
+ - @projects.each do |project|
+ - unless entities_per_project(project, entity).zero?
+ %li{class: ("active" if params[:project_id] == project.id.to_s)}
+ = link_to filter_path(entity, project_id: project.id) do
+ = project.name_with_namespace
+ %small.pull-right= entities_per_project(project, entity)
+
+ %fieldset
+ - if params[:status].present? || params[:project_id].present?
+ = link_to filter_path(entity, status: nil, project_id: nil), class: 'pull-right cgray' do
+ %i.icon-remove
+ %strong Clear filter
+
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
new file mode 100644
index 00000000000..3b3888a50e9
--- /dev/null
+++ b/app/views/shared/_issues.html.haml
@@ -0,0 +1,15 @@
+- if @issues.any?
+ - @issues.group_by(&:project).each do |group|
+ .ui-box.small-box
+ - project = group[0]
+ .title
+ = link_to_project project
+ = link_to 'show all', project_issues_path(project), class: 'pull-right'
+
+ %ul.well-list.issues-list
+ - group[1].each do |issue|
+ = render 'projects/issues/issue', issue: issue
+ = paginate @issues, theme: "gitlab"
+- else
+ %p.nothing_here_message No issues to show
+
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index 85391a34316..b7a7ca8fcc8 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -1,14 +1,13 @@
- if @merge_requests.any?
- - @merge_requests.group_by(&:project).each do |group|
- .ui-box
+ - @merge_requests.group_by(&:target_project).each do |group|
+ .ui-box.small-box
- project = group[0]
- %h5.title
+ .title
= link_to_project project
- %ul.well-list
+ %ul.well-list.mr-list
- group[1].each do |merge_request|
- = render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request})
- %hr
+ = render 'projects/merge_requests/merge_request', merge_request: merge_request
= paginate @merge_requests, theme: "gitlab"
- else
- %h3.nothing_here_message Nothing to show here
+ %h3.nothing_here_message No merge requests to show
diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml
index 5fdcea850b2..6d363807d62 100644
--- a/app/views/shared/_no_ssh.html.haml
+++ b/app/views/shared/_no_ssh.html.haml
@@ -1,3 +1,3 @@
-- if current_user.require_ssh_key?
- %p.error_message.centered
- You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_key_path} to your profile
+- if current_user.require_ssh_key? && alert.blank? && notice.blank?
+ %p.error-message.centered
+ You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile
diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml
new file mode 100644
index 00000000000..f3d032ef986
--- /dev/null
+++ b/app/views/shared/_project_filter.html.haml
@@ -0,0 +1,32 @@
+= form_tag project_entities_path, method: 'get' do
+ %fieldset
+ - if current_user
+ %ul.nav.nav-pills.nav-stacked
+ %li{class: ("active" if params[:scope].blank?)}
+ = link_to project_filter_path(scope: nil) do
+ Everyone's
+ %li{class: ("active" if params[:scope] == 'assigned-to-me')}
+ = link_to project_filter_path(scope: 'assigned-to-me') do
+ Assigned to me
+ %li{class: ("active" if params[:scope] == 'created-by-me')}
+ = link_to project_filter_path(scope: 'created-by-me') do
+ Created by me
+
+ %ul.nav.nav-pills.nav-stacked
+ %li{class: ("active" if params[:state].blank?)}
+ = link_to project_filter_path(state: nil) do
+ Open
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to project_filter_path(state: 'closed') do
+ Closed
+ %li{class: ("active" if params[:state] == 'all')}
+ = link_to project_filter_path(state: 'all') do
+ All
+
+ %fieldset
+ - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any?
+ = link_to project_entities_path, class: 'cgray pull-right' do
+ %i.icon-remove
+ %strong Clear filter
+
+
diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml
new file mode 100644
index 00000000000..c97f8ba0f0e
--- /dev/null
+++ b/app/views/shared/_promo.html.haml
@@ -0,0 +1,4 @@
+.gitlab-promo
+ = link_to "Homepage", "http://gitlab.org"
+ = link_to "Blog", "http://blog.gitlab.org"
+ = link_to "@gitlabhq", "https://twitter.com/gitlabhq"
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index 8b44cf1944e..dc8c656e12e 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -3,3 +3,5 @@
= hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
+ - @options && @options.each do |key, value|
+ = hidden_field_tag key, value, id: nil
diff --git a/app/views/snippets/_blob.html.haml b/app/views/snippets/_blob.html.haml
index 017a33b34f3..c2e0d97a117 100644
--- a/app/views/snippets/_blob.html.haml
+++ b/app/views/snippets/_blob.html.haml
@@ -1,10 +1,14 @@
-.file_holder
- .file_title
+.file-holder
+ .file-title
%i.icon-file
%strong= @snippet.file_name
%span.options
- = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank"
- .file_content.code
+ .btn-group.tree-btn-group.pull-right
+ - if @snippet.author == current_user
+ = link_to "Edit", edit_snippet_path(@snippet), class: "btn btn-tiny", title: 'Edit Snippet'
+ = link_to "Delete", snippet_path(@snippet), method: :delete, confirm: "Are you sure?", class: "btn btn-tiny", title: 'Delete Snippet'
+ = link_to "Raw", raw_snippet_path(@snippet), class: "btn btn-tiny", target: "_blank"
+ .file-content.code
- unless @snippet.content.empty?
%div{class: user_color_scheme_class}
= raw @snippet.colorize(formatter: :gitlab)
diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml
index 77162cdcde3..e77550e7be3 100644
--- a/app/views/snippets/_form.html.haml
+++ b/app/views/snippets/_form.html.haml
@@ -1,36 +1,41 @@
-%h3.page_title
+%h3.page-title
= @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}"
%hr
.snippet-form-holder
- = form_for [@project, @snippet] do |f|
+ = form_for @snippet, as: :personal_snippet, url: url do |f|
-if @snippet.errors.any?
.alert.alert-error
%ul
- @snippet.errors.full_messages.each do |msg|
%li= msg
- .clearfix
+ .control-group
= f.label :title
- .input= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true
- .clearfix
- = f.label "Lifetime"
- .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'}
- .clearfix
+ .controls= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true
+ .control-group
+ = f.label "Private?"
+ .controls
+ = f.check_box :private, {class: ''}
+ .control-group
.file-editor
= f.label :file_name, "File"
- .input
- .file_holder.snippet
- .file_title
+ .controls
+ .file-holder.snippet
+ .file-title
= f.text_field :file_name, placeholder: "example.rb", class: 'snippet-file-name', required: true
- .file_content.code
+ .file-content.code
%pre#editor= @snippet.content
= f.hidden_field :content, class: 'snippet-file-content'
.form-actions
- = f.submit 'Save', class: "btn-save btn"
- = link_to "Cancel", project_snippets_path(@project), class: " btn"
+ - if @snippet.new_record?
+ = f.submit 'Create snippet', class: "btn-create btn"
+ - else
+ = f.submit 'Save', class: "btn-save btn"
+
+ = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
- unless @snippet.new_record?
- .pull-right= link_to 'Destroy', [@project, @snippet], confirm: 'Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
+ .pull-right= link_to 'Destroy', snippet_path(@snippet), confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
:javascript
diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml
index a576500c15d..9689c9c4d38 100644
--- a/app/views/snippets/_snippet.html.haml
+++ b/app/views/snippets/_snippet.html.haml
@@ -1,13 +1,23 @@
-%tr
- %td
- = image_tag gravatar_icon(snippet.author_email), class: "avatar s24"
- %a{href: project_snippet_path(snippet.project, snippet)}
- %strong= truncate(snippet.title, length: 60)
- %td
- = snippet.file_name
- %td
- %span.cgray
- - if snippet.expires_at
- = snippet.expires_at.to_date.to_s(:short)
- - else
- Never
+%li
+ %h4.snippet-title
+ = link_to reliable_snippet_path(snippet) do
+ = truncate(snippet.title, length: 60)
+ - if snippet.private?
+ %span.label.label-success
+ %i.icon-lock
+ private
+ %span.cgray.monospace.tiny.pull-right
+ = snippet.file_name
+
+ %small.pull-right.cgray
+ - if snippet.project_id?
+ = link_to snippet.project.name_with_namespace, project_path(snippet.project)
+
+ .snippet-info
+ = "##{snippet.id}"
+ %span
+ by
+ = link_to user_snippets_path(snippet.author) do
+ = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
+ = snippet.author_name
+ %span.light #{time_ago_in_words(snippet.created_at)} ago
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
new file mode 100644
index 00000000000..05365dd8b88
--- /dev/null
+++ b/app/views/snippets/_snippets.html.haml
@@ -0,0 +1,7 @@
+%ul.bordered-list
+ = render partial: 'snippet', collection: @snippets
+ - if @snippets.empty?
+ %li
+ %h3.nothing_here_message 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
new file mode 100644
index 00000000000..51030f965a1
--- /dev/null
+++ b/app/views/snippets/current_user_index.html.haml
@@ -0,0 +1,34 @@
+%h3.page-title
+ My Snippets
+ .pull-right
+ = link_to new_snippet_path, class: "btn btn-new grouped", title: "New Snippet" do
+ Add new snippet
+ = link_to snippets_path, class: "btn grouped" do
+ Discover snippets
+
+%p.light
+ Share code pastes with others out of git repository
+%hr
+
+.row
+ .span3
+ %ul.nav.nav-pills.nav-stacked
+ = nav_tab :scope, nil do
+ = link_to user_snippets_path(@user) do
+ All
+ %span.pull-right
+ = @user.snippets.count
+ = nav_tab :scope, 'private' do
+ = link_to user_snippets_path(@user, scope: 'private') do
+ Private
+ %span.pull-right
+ = @user.snippets.private.count
+ = nav_tab :scope, 'public' do
+ = link_to user_snippets_path(@user, scope: 'public') do
+ Public
+ %span.pull-right
+ = @user.snippets.public.count
+
+ .span9.my-snippets
+ = render 'snippets'
+
diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml
index 8afaf46e95d..1b88a85faf1 100644
--- a/app/views/snippets/edit.html.haml
+++ b/app/views/snippets/edit.html.haml
@@ -1,2 +1 @@
-= render "projects/project_head"
-= render "snippets/form"
+= render "snippets/form", url: snippet_path(@snippet)
diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml
index 28a533d238f..2f6c914a159 100644
--- a/app/views/snippets/index.html.haml
+++ b/app/views/snippets/index.html.haml
@@ -1,21 +1,15 @@
-= render "projects/project_head"
+%h3.page-title
+ Public snippets
-%h3.page_title
- Snippets
- %small share code pastes with others out of git repository
-
- - if can? current_user, :write_snippet, @project
- = link_to new_project_snippet_path(@project), class: "btn btn-small add_new pull-right", title: "New Snippet" do
+ .pull-right
+ = link_to new_snippet_path, class: "btn btn-new grouped", title: "New Snippet" do
Add new snippet
-%br
-%table
- %thead
- %tr
- %th Title
- %th File Name
- %th Expires At
- = render @snippets
- - if @snippets.empty?
- %tr
- %td{colspan: 3}
- %h3.nothing_here_message Nothing here.
+ = link_to user_snippets_path(current_user), class: "btn grouped" do
+ My snippets
+
+%p.light
+ Public snippets created by you and other users are listed here
+
+%hr
+= render 'snippets'
+
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index 8afaf46e95d..90e0a1f79da 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -1,2 +1 @@
-= render "projects/project_head"
-= render "snippets/form"
+= render "snippets/form", url: snippets_path(@snippet)
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index e6bcd88f830..37f9e7576f5 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -1,11 +1,31 @@
-= render "projects/project_head"
-
-%h3.page_title
+%h3.page-title
= @snippet.title
- %small= @snippet.file_name
- - if can?(current_user, :admin_snippet, @project) || @snippet.author == current_user
- = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-small pull-right"
-%br
+ - if @snippet.private?
+ %span.label.label-success
+ %i.icon-lock
+ private
+
+ .pull-right
+ = link_to new_snippet_path, class: "btn btn-new btn-small", title: "New Snippet" do
+ Add new snippet
+
+
+.append-bottom-20
+ .pull-right
+ = "##{@snippet.id}"
+ %span.light
+ by
+ = link_to user_snippets_path(@snippet.author) do
+ = image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
+ = @snippet.author_name
+
+ .back-link
+ - if @snippet.author == current_user
+ = link_to user_snippets_path(current_user) do
+ &larr; my snippets
+ - else
+ = link_to snippets_path do
+ &larr; discover snippets
+
%div= render 'blob'
-%div#notes= render "notes/notes_with_form"
diff --git a/app/views/snippets/user_index.html.haml b/app/views/snippets/user_index.html.haml
new file mode 100644
index 00000000000..49636c3f5f0
--- /dev/null
+++ b/app/views/snippets/user_index.html.haml
@@ -0,0 +1,12 @@
+%h3.page-title
+ = image_tag gravatar_icon(@user.email), class: "avatar s24"
+ = @user.name
+ %span
+ \/
+ Snippets
+ = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
+ Add new snippet
+
+%hr
+
+= render 'snippets'
diff --git a/app/views/team_members/_form.html.haml b/app/views/team_members/_form.html.haml
deleted file mode 100644
index 05bea2db87e..00000000000
--- a/app/views/team_members/_form.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-%h3.page_title
- = "New Team member(s)"
-%hr
-= form_for @user_project_relation, as: :team_member, url: project_team_members_path(@project) do |f|
- -if @user_project_relation.errors.any?
- .alert.alert-error
- %ul
- - @user_project_relation.errors.full_messages.each do |msg|
- %li= msg
-
- %h6 1. Choose people you want in the team
- .clearfix
- = f.label :user_ids, "People"
- .input= select_tag(:user_ids, options_from_collection_for_select(User.active.not_in_project(@project).alphabetically, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true})
-
- %h6 2. Set access level for them
- .clearfix
- = f.label :project_access, "Project Access"
- .input= select_tag :project_access, options_for_select(Project.access_options, @user_project_relation.project_access), class: "project-access-select chosen"
-
- .actions
- = f.submit 'Add users', class: "btn btn-create"
- = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
diff --git a/app/views/team_members/_show.html.haml b/app/views/team_members/_show.html.haml
deleted file mode 100644
index 3df2caed64a..00000000000
--- a/app/views/team_members/_show.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-- user = member.user
-- allow_admin = can? current_user, :admin_project, @project
-%li{id: dom_id(user), class: "team_member_row user_#{user.id}"}
- .row
- .span6
- = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do
- = image_tag gravatar_icon(user.email, 40), class: "avatar s32"
- = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do
- %strong= truncate(user.name, lenght: 40)
- %br
- %small.cgray= user.email
-
- .span5.pull-right
- - if allow_admin
- .left
- = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f|
- = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2"
- .pull-right
- - if current_user == user
- %span.btn.disabled This is you!
- - if @project.namespace_owner == user
- %span.btn.disabled Owner
- - elsif user.blocked
- %span.btn.disabled.blocked Blocked
- - elsif allow_admin
- = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove" do
- %i.icon-minus.icon-white
-
diff --git a/app/views/team_members/_show_team.html.haml b/app/views/team_members/_show_team.html.haml
deleted file mode 100644
index f1555f0b87b..00000000000
--- a/app/views/team_members/_show_team.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-- team = team_rel.user_team
-- allow_admin = can? current_user, :admin_team_member, @project
-%li{id: dom_id(team), class: "user_team_row team_#{team.id}"}
- .row
- .span6
- %strong= link_to team.name, team_path(team), title: team.name, class: "dark"
- %br
- %small.cgray Members: #{team.members.count}
-
- .span5.pull-right
- .pull-right
- - if allow_admin
- .left
- = link_to resign_project_team_path(@project, team), method: :delete, confirm: "Are you shure?", class: "btn btn-remove small" do
- %i.icon-minus.icon-white
diff --git a/app/views/team_members/_team.html.haml b/app/views/team_members/_team.html.haml
deleted file mode 100644
index 365d9b65942..00000000000
--- a/app/views/team_members/_team.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- grouper_project_members(@project).each do |access, members|
- .ui-box
- %h5.title
- = Project.access_options.key(access).pluralize
- %small= members.size
- %ul.well-list
- - members.sort_by(&:user_name).each do |up|
- = render(partial: 'team_members/show', locals: {member: up})
-
-
-:javascript
- $(function(){
- $('.repo-access-select, .project-access-select').live("change", function() {
- $(this.form).submit();
- });
- })
diff --git a/app/views/team_members/_teams.html.haml b/app/views/team_members/_teams.html.haml
deleted file mode 100644
index 156fdd1befa..00000000000
--- a/app/views/team_members/_teams.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- grouper_project_teams(@project).each do |access, teams|
- .ui-box
- %h5.title
- = UserTeam.access_roles.key(access).pluralize
- %small= teams.size
- %ul.well-list
- - teams.sort_by(&:team_name).each do |tofr|
- = render(partial: 'team_members/show_team', locals: {team_rel: tofr})
-
-
-:javascript
- $(function(){
- $('.repo-access-select, .project-access-select').live("change", function() {
- $(this.form).submit();
- });
- })
diff --git a/app/views/team_members/create.js.haml b/app/views/team_members/create.js.haml
deleted file mode 100644
index b7dff35a269..00000000000
--- a/app/views/team_members/create.js.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-- if @user_project_relation.valid?
- :plain
- $("#new_team_member").hide("slide", { direction: "right" }, 150, function(){
- $("#team-table").show("slide", { direction: "left" }, 150, function() {
- $("#new_team_member").remove();
- $("#team-table").replaceWith("#{escape_javascript(render('projects/team'))}");
- $(".add_new").show();
- });
- });
-- else
- :plain
- $("#new_team_member").replaceWith("#{escape_javascript(render('form'))}");
- $('select#team_member_user_id').chosen();
diff --git a/app/views/team_members/import.html.haml b/app/views/team_members/import.html.haml
deleted file mode 100644
index d6c81befd08..00000000000
--- a/app/views/team_members/import.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-= render "projects/project_head"
-
-%h3.page_title
- = "Import team from another project"
-%hr
-%p.slead
- Read more about project team import #{link_to "here", '#', class: 'vlink'}.
-= form_tag apply_import_project_team_members_path(@project), method: 'post' do
- %p.slead Choose project you want to use as team source:
- .padded
- = label_tag :source_project_id, "Project"
- .input= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "chosen xxlarge", required: true)
-
- .actions
- = submit_tag 'Import', class: "btn btn-save"
- = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
-
diff --git a/app/views/team_members/index.html.haml b/app/views/team_members/index.html.haml
deleted file mode 100644
index 3264f58cb32..00000000000
--- a/app/views/team_members/index.html.haml
+++ /dev/null
@@ -1,33 +0,0 @@
-= render "projects/project_head"
-%h3.page_title
- Team Members
- (#{@project.users.count})
- %small
- Read more about project permissions
- %strong= link_to "here", help_permissions_path, class: "vlink"
-
- - if can? current_user, :admin_team_member, @project
- %span.pull-right
- = link_to import_project_team_members_path(@project), class: "btn btn-small grouped", title: "Import team from another project" do
- Import team from another project
- = link_to available_project_teams_path(@project), class: "btn btn-small grouped", title: "Assign project to team of users" do
- Assign project to Team of users
- = link_to new_project_team_member_path(@project), class: "btn btn-primary small grouped", title: "New Team Member" do
- New Team Member
-
-%hr
-
-.clearfix
-%div.team-table
- = render partial: "team_members/team", locals: {project: @project}
-
-
-%h3.page_title
- Assigned teams
- (#{@project.user_teams.count})
-
-%hr
-
-.clearfix
-%div.team-table
- = render partial: "team_members/teams", locals: {project: @project}
diff --git a/app/views/team_members/new.html.haml b/app/views/team_members/new.html.haml
deleted file mode 100644
index 40eb4cebf08..00000000000
--- a/app/views/team_members/new.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-= render "projects/project_head"
-= render "team_members/form"
diff --git a/app/views/team_members/show.html.haml b/app/views/team_members/show.html.haml
deleted file mode 100644
index 192948eff7d..00000000000
--- a/app/views/team_members/show.html.haml
+++ /dev/null
@@ -1,59 +0,0 @@
-- allow_admin = can? current_user, :admin_project, @project
-
-.team_member_show
- - if can? current_user, :admin_project, @project
- = link_to 'Remove from team', project_team_member_path(@project, @member), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove pull-right"
- .profile_avatar_holder
- = image_tag gravatar_icon(@member.email, 60), class: "borders"
- %h3.page_title
- = @member.name
- %small (@#{@member.username})
-
- %hr
- .back_link
- %br
- = link_to project_team_index_path(@project), class: "" do
- &larr; To team list
- %br
- .row
- .span6
- %table.lite
- %tr
- %td Email
- %td= mail_to @member.email
- %tr
- %td Skype
- %td= @member.skype
- - unless @member.linkedin.blank?
- %tr
- %td LinkedIn
- %td= @member.linkedin
- - unless @member.twitter.blank?
- %tr
- %td Twitter
- %td= @member.twitter
- - unless @member.bio.blank?
- %tr
- %td Bio
- %td= @member.bio
- .span6
- %table.lite
- %tr
- %td Member since
- %td= @user_project_relation.created_at.stamp("Aug 21, 2011")
- %tr
- %td
- Project Access:
- %small (#{link_to "read more", help_permissions_path, class: "vlink"})
- %td
- = form_for(@user_project_relation, as: :team_member, url: project_team_member_path(@project, @member)) do |f|
- = f.select :project_access, options_for_select(Project.access_options, @user_project_relation.project_access), {}, class: "project-access-select", disabled: !allow_admin
- %hr
- = render @events
-:javascript
- $(function(){
- $('.repo-access-select, .project-access-select').live("change", function() {
- $(this.form).submit();
- });
- })
-
diff --git a/app/views/teams/_filter.html.haml b/app/views/teams/_filter.html.haml
deleted file mode 100644
index f461fcad42e..00000000000
--- a/app/views/teams/_filter.html.haml
+++ /dev/null
@@ -1,33 +0,0 @@
-= form_tag team_filter_path(entity), method: 'get' do
- %fieldset.dashboard-search-filter
- = search_field_tag "search", params[:search], { placeholder: 'Search', class: 'search-text-input' }
- = button_tag type: 'submit', class: 'btn' do
- %i.icon-search
-
- %fieldset
- %legend Status:
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if !params[:status])}
- = link_to team_filter_path(entity, status: nil) do
- Open
- %li{class: ("active" if params[:status] == 'closed')}
- = link_to team_filter_path(entity, status: 'closed') do
- Closed
- %li{class: ("active" if params[:status] == 'all')}
- = link_to team_filter_path(entity, status: 'all') do
- All
-
- %fieldset
- %legend Projects:
- %ul.nav.nav-pills.nav-stacked
- - @projects.each do |project|
- - unless entities_per_project(project, entity).zero?
- %li{class: ("active" if params[:project_id] == project.id.to_s)}
- = link_to team_filter_path(entity, project_id: project.id) do
- = project.name_with_namespace
- %small.pull-right= entities_per_project(project, entity)
-
- %fieldset
- %hr
- = link_to "Reset", team_filter_path(entity), class: 'btn pull-right'
-
diff --git a/app/views/teams/_projects.html.haml b/app/views/teams/_projects.html.haml
deleted file mode 100644
index 5677255b15b..00000000000
--- a/app/views/teams/_projects.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-.projects_box
- %h5.title
- Projects
- %small
- (#{projects.count})
- - if can? current_user, :manage_user_team, @team
- %span.pull-right
- = link_to new_team_project_path(@team), class: "btn btn-tiny info" do
- %i.icon-plus
- Assign Project
- %ul.well-list
- - if projects.blank?
- %p.nothing_here_message This team has no projects yet
- - projects.each do |project|
- %li
- = link_to project_path(project), class: dom_class(project) do
- %strong.well-title= truncate(project.name, length: 25)
- %span.arrow
- &rarr;
- %span.last_activity
- %strong Last activity:
- %span= project_last_activity(project)
diff --git a/app/views/teams/edit.html.haml b/app/views/teams/edit.html.haml
deleted file mode 100644
index 3435583600d..00000000000
--- a/app/views/teams/edit.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-%h3.page_title= "Edit Team #{@team.name}"
-%hr
-= form_for @team, url: team_path(@team) do |f|
- - if @team.errors.any?
- .alert.alert-error
- %span= @team.errors.full_messages.first
- .clearfix
- = f.label :name do
- Team name is
- .input
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
-
- .clearfix
- = f.label :path do
- Team path is
- .input
- = f.text_field :path, placeholder: "opensource", class: "xxlarge left"
- .form-actions
- = f.submit 'Save team changes', class: "btn btn-primary"
- = link_to 'Delete team', team_path(@team), method: :delete, confirm: "You are shure?", class: "btn btn-remove pull-right"
diff --git a/app/views/teams/issues.html.haml b/app/views/teams/issues.html.haml
deleted file mode 100644
index c6a68c37b9c..00000000000
--- a/app/views/teams/issues.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-%h3.page_title
- Issues
- %small (in Team projects assigned to Team members)
- %small.pull-right #{@issues.total_count} issues
-
-%hr
-.row
- .span3
- = render 'filter', entity: 'issue'
- .span9
- - if @issues.any?
- - @issues.group_by(&:project).each do |group|
- %div.ui-box
- - @project = group[0]
- %h5.title
- = link_to_project @project
- %ul.well-list.issues_table
- - group[1].each do |issue|
- = render(partial: 'issues/show', locals: {issue: issue})
- %hr
- = paginate @issues, theme: "gitlab"
- - else
- %p.nothing_here_message Nothing to show here
diff --git a/app/views/teams/members/_form.html.haml b/app/views/teams/members/_form.html.haml
deleted file mode 100644
index c22ee78305f..00000000000
--- a/app/views/teams/members/_form.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-= form_tag admin_team_member_path(@team, @member), method: :put do
- -if @member.errors.any?
- .alert.alert-error
- %ul
- - @member.errors.full_messages.each do |msg|
- %li= msg
-
- .clearfix
- %label Default access for Team projects:
- .input
- = select_tag :default_project_access, options_for_select(UserTeam.access_roles, @team.default_projects_access(@member)), class: "project-access-select chosen span3"
- .clearfix
- %label Team admin?
- .input
- = check_box_tag :group_admin, true, @team.admin?(@member)
-
- %br
- .actions
- = submit_tag 'Save', class: "btn btn-save"
- = link_to 'Cancel', :back, class: "btn"
diff --git a/app/views/teams/members/_show.html.haml b/app/views/teams/members/_show.html.haml
deleted file mode 100644
index 6cddb8e4826..00000000000
--- a/app/views/teams/members/_show.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-- user = member.user
-- allow_admin = can? current_user, :manage_user_team, @team
-%li{id: dom_id(member), class: "team_member_row user_#{user.id}"}
- .row
- .span5
- = link_to user_path(user.username), title: user.name, class: "dark" do
- = image_tag gravatar_icon(user.email, 40), class: "avatar s32"
- = link_to user_path(user.username), title: user.name, class: "dark" do
- %strong= truncate(user.name, lenght: 40)
- %br
- %small.cgray= user.email
-
- .span6.pull-right
- - if allow_admin
- .left.span2
- = form_for(member, as: :team_member, url: team_member_path(@team, user)) do |f|
- = f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "medium project-access-select span2"
- .left.span2
- %span
- = check_box_tag :group_admin, true, @team.admin?(user)
- Admin access
- .pull-right
- - if current_user == user
- %span.btn.disabled This is you!
- - if @team.owner == user
- %span.btn.disabled.btn-success Owner
- - elsif user.blocked
- %span.btn.disabled.blocked Blocked
- - elsif allow_admin
- = link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "btn-tiny btn btn-remove" do
- %i.icon-minus.icon-white
diff --git a/app/views/teams/members/_team.html.haml b/app/views/teams/members/_team.html.haml
deleted file mode 100644
index d8afc1fa371..00000000000
--- a/app/views/teams/members/_team.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- grouped_user_team_members(@team).each do |access, members|
- .ui-box
- %h5.title
- = Project.access_options.key(access).pluralize
- %small= members.size
- %ul.well-list
- - members.sort_by(&:user_name).each do |up|
- = render(partial: 'teams/members/show', locals: {member: up})
-
-
-:javascript
- $(function(){
- $('.repo-access-select, .project-access-select').live("change", function() {
- $(this.form).submit();
- });
- })
diff --git a/app/views/teams/members/edit.html.haml b/app/views/teams/members/edit.html.haml
deleted file mode 100644
index 375880496ab..00000000000
--- a/app/views/teams/members/edit.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-%h3.page_title
- Edit access #{@member.name} in #{@team.name} team
-
-%hr
-%table.zebra-striped
- %tr
- %td User:
- %td= @member.name
- %tr
- %td Team:
- %td= @team.name
- %tr
- %td Since:
- %td= member_since(@team, @member).stamp("Nov 11, 2010")
-
-= render 'form'
diff --git a/app/views/teams/members/index.html.haml b/app/views/teams/members/index.html.haml
deleted file mode 100644
index 87438266cfb..00000000000
--- a/app/views/teams/members/index.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-%h3.page_title
- Team Members
- (#{@members.count})
- %small
- Read more about project permissions
- %strong= link_to "here", help_permissions_path, class: "vlink"
-
- - if can? current_user, :manage_user_team, @team
- %span.pull-right
- = link_to new_team_member_path(@team), class: "btn btn-primary small grouped", title: "New Team Member" do
- New Team Member
-%hr
-
-
-.clearfix
-%div.team-table
- = render partial: "teams/members/team", locals: {project: @team}
diff --git a/app/views/teams/members/new.html.haml b/app/views/teams/members/new.html.haml
deleted file mode 100644
index 083e137e0ae..00000000000
--- a/app/views/teams/members/new.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-%h3.page_title
- Team: #{@team.name}
-
-%fieldset
- %legend Members (#{@team.members.count})
- = form_tag team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do
- %table#members_list
- %thead
- %tr
- %th User name
- %th Default project access
- %th Team access
- %th
- - @team.members.each do |member|
- %tr.member
- %td
- = member.name
- %small= "(#{member.email})"
- %td= @team.human_default_projects_access(member)
- %td= @team.admin?(member) ? "Admin" : "Member"
- %td
- %tr
- %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_email), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
- %td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
- %td
- %span= check_box_tag :group_admin
- %span Admin?
- %td= submit_tag 'Add User', class: "btn btn-create", id: :add_members_to_team
diff --git a/app/views/teams/members/show.html.haml b/app/views/teams/members/show.html.haml
deleted file mode 100644
index f760c2dae3a..00000000000
--- a/app/views/teams/members/show.html.haml
+++ /dev/null
@@ -1,60 +0,0 @@
-- allow_admin = can? current_user, :admin_project, @project
-- user = @team_member.user
-
-.team_member_show
- - if can? current_user, :admin_project, @project
- = link_to 'Remove from team', project_team_member_path(project_id: @project, id: @team_member.id), confirm: 'Are you sure?', method: :delete, class: "pull-right btn btn-remove"
- .profile_avatar_holder
- = image_tag gravatar_icon(user.email, 60), class: "borders"
- %h3.page_title
- = user.name
- %small (@#{user.username})
-
- %hr
- .back_link
- %br
- = link_to project_team_index_path(@project), class: "" do
- &larr; To team list
- %br
- .row
- .span6
- %table.lite
- %tr
- %td Email
- %td= mail_to user.email
- %tr
- %td Skype
- %td= user.skype
- - unless user.linkedin.blank?
- %tr
- %td LinkedIn
- %td= user.linkedin
- - unless user.twitter.blank?
- %tr
- %td Twitter
- %td= user.twitter
- - unless user.bio.blank?
- %tr
- %td Bio
- %td= user.bio
- .span6
- %table.lite
- %tr
- %td Member since
- %td= @team_member.created_at.stamp("Aug 21, 2011")
- %tr
- %td
- Project Access:
- %small (#{link_to "read more", help_permissions_path, class: "vlink"})
- %td
- = form_for(@team_member, as: :team_member, url: project_team_member_path(@project, @team_member)) do |f|
- = f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select", disabled: !allow_admin
- %hr
- = render @events
-:javascript
- $(function(){
- $('.repo-access-select, .project-access-select').live("change", function() {
- $(this.form).submit();
- });
- })
-
diff --git a/app/views/teams/merge_requests.html.haml b/app/views/teams/merge_requests.html.haml
deleted file mode 100644
index 417d1aa6040..00000000000
--- a/app/views/teams/merge_requests.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-%h3.page_title
- Merge Requests
- %small (authored by or assigned to Team members)
- %small.pull-right #{@merge_requests.total_count} merge requests
-
-%hr
-.row
- .span3
- = render 'filter', entity: 'merge_request'
- .span9
- - if @merge_requests.any?
- - @merge_requests.group_by(&:project).each do |group|
- .ui-box
- - @project = group[0]
- %h5.title
- = link_to_project @project
- %ul.well-list
- - group[1].each do |merge_request|
- = render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request})
- %hr
- = paginate @merge_requests, theme: "gitlab"
-
- - else
- %h3.nothing_here_message Nothing to show here
diff --git a/app/views/teams/new.html.haml b/app/views/teams/new.html.haml
deleted file mode 100644
index 38f61c11c0c..00000000000
--- a/app/views/teams/new.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-%h3.page_title New Team
-%hr
-= form_for @team, url: teams_path do |f|
- - if @team.errors.any?
- .alert.alert-error
- %span= @team.errors.full_messages.first
- .clearfix
- = f.label :name do
- Team name is
- .input
- = f.text_field :name, placeholder: "Ex. Ruby Developers", class: "xxlarge left"
- &nbsp;
- = f.submit 'Create team', class: "btn btn-create"
- %hr
- .padded
- %ul
- %li All created teams are public (users can view who enter into team and which project are assigned for this team)
- %li People within a team see only projects they have access to
- %li You will be able to assign existing projects for team
diff --git a/app/views/teams/projects/_form.html.haml b/app/views/teams/projects/_form.html.haml
deleted file mode 100644
index d2c89b0c36b..00000000000
--- a/app/views/teams/projects/_form.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-= form_tag team_project_path(@team, @project), method: :put do
- -if @project.errors.any?
- .alert.alert-error
- %ul
- - @project.errors.full_messages.each do |msg|
- %li= msg
-
- .clearfix
- %label Max access for Team members:
- .input
- = select_tag :greatest_project_access, options_for_select(UserTeam.access_roles, @team.max_project_access(@project)), class: "project-access-select chosen span3"
-
- %br
- .actions
- = submit_tag 'Save', class: "btn btn-save"
- = link_to 'Cancel', :back, class: "btn btn-cancel"
diff --git a/app/views/teams/projects/edit.html.haml b/app/views/teams/projects/edit.html.haml
deleted file mode 100644
index 82c7d734815..00000000000
--- a/app/views/teams/projects/edit.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-%h3.page_title
- Edit max access in #{link_to @project.name_with_namespace, @project} for #{link_to(@team.name, team_path(@team))} team
-
-%hr
-
-= render 'form'
diff --git a/app/views/teams/projects/index.html.haml b/app/views/teams/projects/index.html.haml
deleted file mode 100644
index 696ee29c778..00000000000
--- a/app/views/teams/projects/index.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-%h3.page_title
- Assigned projects (#{@team.projects.count})
- %small
- Read more about project permissions
- %strong= link_to "here", help_permissions_path, class: "vlink"
-
- - if current_user.can?(:manage_user_team, @team) && @avaliable_projects.any?
- %span.pull-right
- = link_to new_team_project_path(@team), class: "btn btn-primary small grouped", title: "New Team Member" do
- Assign project to Team
-
-%hr
-
-- if @team.projects.present?
- %table.projects-table
- %thead
- %tr
- %th Project name
- %th Max access
- - if current_user.can?(:admin_user_team, @team)
- %th.span3
-
- - @team.projects.each do |project|
- %tr.project
- %td
- = link_to project.name_with_namespace, project_path(project)
- %td
- %span= @team.human_max_project_access(project)
-
- - if current_user.can?(:admin_user_team, @team)
- %td.bgred
- = link_to 'Edit max access', edit_team_project_path(@team, project), class: "btn btn-small"
- = link_to 'Relegate', team_project_path(@team, project), confirm: 'Remove project from team and move to global namespace. Are you sure?', method: :delete, class: "btn btn-remove small"
-
-- else
- %p.nothing_here_message This team has no projects yet
diff --git a/app/views/teams/projects/new.html.haml b/app/views/teams/projects/new.html.haml
deleted file mode 100644
index 3f3671aa0a4..00000000000
--- a/app/views/teams/projects/new.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-%h3.page_title
- Team: #{@team.name}
-
-%fieldset
- %legend Projects (#{@team.projects.count})
- = form_tag team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do
- %table#projects_list
- %thead
- %tr
- %th Project name
- %th Max access
- %th
- - @team.projects.each do |project|
- %tr.project
- %td
- = link_to project.name_with_namespace, team_project_path(@team, project)
- %td
- %span= @team.human_max_project_access(project)
- %td
- %tr
- %td= select_tag :project_ids, options_from_collection_for_select(@avaliable_projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
- %td= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles), {class: "project-access-select chosen span3" }
- %td= submit_tag 'Add Project', class: "btn btn-create", id: :assign_projects_to_team
diff --git a/app/views/teams/show.html.haml b/app/views/teams/show.html.haml
deleted file mode 100644
index d6e80e2a51e..00000000000
--- a/app/views/teams/show.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-.projects
- .activities.span8
- = link_to dashboard_path, class: 'btn btn-tiny' do
- &larr; To dashboard
- &nbsp;
- %span.cgray Events and projects are filtered in scope of team
- %hr
- - if @events.any?
- .content_list
- - else
- %p.nothing_here_message Projects activity will be displayed here
- .loading.hide
- .side.span4
- = render "projects", projects: @projects
- %div
- %span.rss-icon
- = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do
- = image_tag "rss_ui.png", title: "feed"
- %strong News Feed
-
- %hr
- .gitlab-promo
- = link_to "Homepage", "http://gitlabhq.com"
- = link_to "Blog", "http://blog.gitlabhq.com"
- = link_to "@gitlabhq", "https://twitter.com/gitlabhq"
-
-:javascript
- $(function(){ Pager.init(20, true); });
diff --git a/app/views/teams/show.js.haml b/app/views/teams/show.js.haml
deleted file mode 100644
index 7e5a148e5ef..00000000000
--- a/app/views/teams/show.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
- Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}");
diff --git a/app/views/tree/_blob.html.haml b/app/views/tree/_blob.html.haml
deleted file mode 100644
index ebf1ee2c678..00000000000
--- a/app/views/tree/_blob.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-.file_holder
- .file_title
- %i.icon-file
- %span.file_name
- = blob.name
- %small= number_to_human_size blob.size
- %span.options= render "tree/blob_actions"
- - if blob.text?
- = render "tree/blob/text", blob: blob
- - elsif blob.image?
- = render "tree/blob/image", blob: blob
- - else
- = render "tree/blob/download", blob: blob
diff --git a/app/views/tree/_blob_actions.html.haml b/app/views/tree/_blob_actions.html.haml
deleted file mode 100644
index 0bde968d0e6..00000000000
--- a/app/views/tree/_blob_actions.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-.btn-group.tree-btn-group
- -# only show edit link for text files
- - if @tree.text?
- = link_to "edit", edit_project_tree_path(@project, @id), class: "btn btn-tiny", disabled: !allowed_tree_edit?
- = link_to "raw", project_blob_path(@project, @id), class: "btn btn-tiny", target: "_blank"
- -# only show normal/blame view links for text files
- - if @tree.text?
- - if current_page? project_blame_path(@project, @id)
- = link_to "normal view", project_tree_path(@project, @id), class: "btn btn-tiny"
- - else
- = link_to "blame", project_blame_path(@project, @id), class: "btn btn-tiny"
- = link_to "history", project_commits_path(@project, @id), class: "btn btn-tiny"
diff --git a/app/views/tree/_head.html.haml b/app/views/tree/_head.html.haml
deleted file mode 100644
index 32c3882400e..00000000000
--- a/app/views/tree/_head.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-%ul.nav.nav-tabs
- %li
- = render partial: 'shared/ref_switcher', locals: {destination: 'tree', path: @path}
- = nav_link(controller: :tree) do
- = link_to 'Source', project_tree_path(@project, @ref)
- %li.pull-right
- = render "shared/clone_panel"
diff --git a/app/views/tree/_tree.html.haml b/app/views/tree/_tree.html.haml
deleted file mode 100644
index 29a2ed02d31..00000000000
--- a/app/views/tree/_tree.html.haml
+++ /dev/null
@@ -1,48 +0,0 @@
-%ul.breadcrumb
- %li
- %span.arrow
- = link_to project_tree_path(@project, @ref) do
- = @project.name
- - tree.breadcrumbs(6) do |title, path|
- \/
- %li
- - if path
- = link_to truncate(title, length: 40), project_tree_path(@project, path)
- - else
- = link_to title, '#'
-
-.clear
-%div.tree_progress
-
-%div#tree-content-holder.tree-content-holder
- - if tree.is_blob?
- = render "tree/blob", blob: tree
- - else
- %table#tree-slider{class: "table_#{@hex_path} tree-table" }
- %thead
- %tr
- %th Name
- %th Last Update
- %th Last Commit
- %th= link_to "history", project_commits_path(@project, @id), class: "btn btn-tiny pull-right"
-
- - if tree.up_dir?
- %tr.tree-item
- %td.tree-item-file-name
- = image_tag "file_empty.png", size: '16x16'
- = link_to "..", project_tree_path(@project, tree.up_dir_path)
- %td
- %td
- %td
-
- = render_tree(tree.contents)
-
- - if tree.readme
- = render "tree/readme", readme: tree.readme
-
-- unless tree.is_blob?
- :javascript
- // Load last commit log for each file in tree
- $(window).load(function(){
- ajaxGet('#{@logs_path}');
- });
diff --git a/app/views/tree/edit.html.haml b/app/views/tree/edit.html.haml
deleted file mode 100644
index 81918e509b8..00000000000
--- a/app/views/tree/edit.html.haml
+++ /dev/null
@@ -1,44 +0,0 @@
-.file-editor
- = form_tag(project_tree_path(@project, @id), method: :put, class: "form-horizontal") do
- .file_holder
- .file_title
- %i.icon-file
- %span.file_name
- = @tree.path
- %small
- on
- %strong= @ref
- %span.options
- .btn-group.tree-btn-group
- = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-tiny btn-cancel", confirm: "Are you sure?"
- .file_content.code
- %pre#editor= @tree.data
-
- .control-group.commit_message-group
- = label_tag 'commit_message', class: "control-label" do
- Commit message
- .controls
- = text_area_tag 'commit_message', '', placeholder: "Update #{@tree.name}", required: true, rows: 3
- .form-actions
- = hidden_field_tag 'last_commit', @last_commit
- = hidden_field_tag 'content', '', id: :file_content
- .commit-button-annotation
- = button_tag "Commit", class: 'btn commit-btn js-commit-button'
- .message
- to branch
- %strong= @ref
- = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", confirm: "Are you sure?"
-
-:javascript
- var ace_mode = "#{@tree.language.try(:ace_mode)}";
- var editor = ace.edit("editor");
- if (ace_mode) {
- editor.getSession().setMode('ace/mode/' + ace_mode);
- }
-
- disableButtonIfEmptyField("#commit_message", ".js-commit-button");
-
- $(".js-commit-button").click(function(){
- $("#file_content").val(editor.getValue());
- $(".file-editor form").submit();
- });
diff --git a/app/views/tree/show.html.haml b/app/views/tree/show.html.haml
deleted file mode 100644
index a4034f22fac..00000000000
--- a/app/views/tree/show.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-= render "head"
-%div#tree-holder.tree-holder
- = render "tree", tree: @tree
diff --git a/app/views/tree/show.js.haml b/app/views/tree/show.js.haml
deleted file mode 100644
index fadd5e2251f..00000000000
--- a/app/views/tree/show.js.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-:plain
- // Load Files list
- $("#tree-holder").html("#{escape_javascript(render(partial: "tree", locals: {tree: @tree}))}");
- $("#tree-content-holder").show("slide", { direction: "right" }, 150);
- $('.project-refs-form #path').val("#{@path}");
-
- // Load last commit log for each file in tree
- $('#tree-slider').waitForImages(function() {
- ajaxGet('#{@logs_path}');
- });
diff --git a/app/views/users/_profile.html.haml b/app/views/users/_profile.html.haml
index de08bc46e70..4cd1eebdf91 100644
--- a/app/views/users/_profile.html.haml
+++ b/app/views/users/_profile.html.haml
@@ -1,23 +1,23 @@
.ui-box
- %h5.title
+ .title
Profile
%ul.well-list
%li
- %strong Email
- %span.pull-right= mail_to user.email
+ %span.light Member since
+ %strong= user.created_at.stamp("Aug 21, 2011")
- unless user.skype.blank?
%li
- %strong Skype
- %span.pull-right= user.skype
+ %span.light Skype:
+ %strong= user.skype
- unless user.linkedin.blank?
%li
- %strong LinkedIn
- %span.pull-right= user.linkedin
+ %span.light LinkedIn:
+ %strong= user.linkedin
- unless user.twitter.blank?
%li
- %strong Twitter
- %span.pull-right= user.twitter
+ %span.light Twitter:
+ %strong= user.twitter
- unless user.bio.blank?
%li
- %strong Bio
- %span.pull-right= user.bio
+ %span.light Bio:
+ %span= user.bio
diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml
index 4bee2f0c8ff..f1b2c8dd7f7 100644
--- a/app/views/users/_projects.html.haml
+++ b/app/views/users/_projects.html.haml
@@ -1,5 +1,5 @@
.ui-box
- %h5.title Projects
+ .title Projects
%ul.well-list
- @projects.each do |project|
%li
@@ -9,12 +9,3 @@
\/
%strong.well-title
= truncate(project.name, length: 45)
- %span.pull-right.light
- - if project.owner == user
- %i.icon-wrench
- - tm = project.team.get_tm(user.id)
- - if tm
- = tm.project_access_human
-%p.light
- %i.icon-wrench
- &ndash; user is a project owner
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 9341737a5ee..743ab0949a1 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,20 +1,20 @@
.row
.span8
- %h3.page_title
- = image_tag gravatar_icon(@user.email, 90), class: "avatar s90"
+ %h3.page-title
+ = image_tag gravatar_icon(@user.email, 90), class: "avatar s90", alt: ''
= @user.name
- if @user == current_user
.pull-right
- = link_to profile_path, class: 'btn btn-small' do
+ = link_to profile_path, class: 'btn' do
%i.icon-edit
Edit Profile
%br
- %small @#{@user.username}
+ %small #{@user.username}
%br
%small member since #{@user.created_at.stamp("Nov 12, 2031")}
.clearfix
%hr
- %h5 Recent events
+ %h4 User Activity:
= render @events
.span4
= render 'profile', user: @user
diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml
new file mode 100644
index 00000000000..5cdb5bb8c40
--- /dev/null
+++ b/app/views/users_groups/_users_group.html.haml
@@ -0,0 +1,23 @@
+- user = member.user
+- return unless user
+%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
+ = image_tag gravatar_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 && can?(current_user, :manage_group, @group) && current_user != user
+ = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do
+ %i.icon-edit
+ = link_to group_users_group_path(@group, member), 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'
diff --git a/app/views/users_groups/update.js.haml b/app/views/users_groups/update.js.haml
new file mode 100644
index 00000000000..5bad48abafd
--- /dev/null
+++ b/app/views/users_groups/update.js.haml
@@ -0,0 +1,2 @@
+:plain
+ $("##{dom_id(@member)}").replaceWith('#{escape_javascript(render(@member, member: @member, show_controls: true))}');
diff --git a/app/views/votes/_votes_inline.html.haml b/app/views/votes/_votes_inline.html.haml
index 91bd200df44..ee805474830 100644
--- a/app/views/votes/_votes_inline.html.haml
+++ b/app/views/votes/_votes_inline.html.haml
@@ -1,6 +1,9 @@
.votes.votes-inline
- .upvotes= votable.upvotes
- .progress
- .bar.bar-success{style: "width: #{votable.upvotes_in_percent}%;"}
- .bar.bar-danger{style: "width: #{votable.downvotes_in_percent}%;"}
- .downvotes= votable.downvotes
+ - unless votable.upvotes.zero?
+ .upvotes
+ + #{votable.upvotes}
+ - unless votable.downvotes.zero?
+ \/
+ - unless votable.downvotes.zero?
+ .downvotes
+ \- #{votable.downvotes}
diff --git a/app/views/wikis/_form.html.haml b/app/views/wikis/_form.html.haml
deleted file mode 100644
index 7758b129b07..00000000000
--- a/app/views/wikis/_form.html.haml
+++ /dev/null
@@ -1,27 +0,0 @@
-= form_for [@project, @wiki] do |f|
- -if @wiki.errors.any?
- #error_explanation
- %h2= "#{pluralize(@wiki.errors.count, "error")} prohibited this wiki from being saved:"
- %ul
- - @wiki.errors.full_messages.each do |msg|
- %li= msg
-
- .ui-box.ui-box-show
- .ui-box-head
- = f.label :title
- .input= f.text_field :title, class: 'span8'
- = f.hidden_field :slug
- .ui-box-body
- .input
- %span.cgray
- Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
- To link to a (new) page you can just type
- %code [Link Title](page-slug)
- \.
-
- .ui-box-bottom
- = f.label :content
- .input= f.text_area :content, class: 'span8 js-gfm-input'
- .actions
- = f.submit 'Save', class: "btn-save btn"
- = link_to "Cancel", project_wiki_path(@project, :index), class: "btn btn-cancel"
diff --git a/app/views/wikis/history.html.haml b/app/views/wikis/history.html.haml
deleted file mode 100644
index 18df8e1d71b..00000000000
--- a/app/views/wikis/history.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-%h3.page_title
- %span.cgray History for
- = @wiki_pages.first.title
-%br
-%table
- %thead
- %tr
- %th Page version
- %th Last updated
- %th Updated by
- %tbody
- - @wiki_pages.each_with_index do |wiki_page, i|
- %tr
- %td
- %strong
- = link_to project_wiki_path(@project, wiki_page, version_id: wiki_page.id) do
- Version
- = @wiki_pages.count - i
- %td
- = wiki_page.created_at.to_s(:short)
- (#{time_ago_in_words(wiki_page.created_at)}
- ago)
- %td= link_to_member(@project, wiki_page.user)
diff --git a/app/views/wikis/pages.html.haml b/app/views/wikis/pages.html.haml
deleted file mode 100644
index 2e0f091ce72..00000000000
--- a/app/views/wikis/pages.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-%h3.page_title All Pages
-%br
-%table
- %thead
- %tr
- %th Title
- %th Slug
- %th Last updated
- %th Updated by
- %tbody
- - @wiki_pages.each do |wiki_page|
- %tr
- %td
- %strong= link_to wiki_page.title, project_wiki_path(@project, wiki_page)
- %td= wiki_page.slug
- %td
- = wiki_page.created_at.to_s(:short) do
- (#{time_ago_in_words(wiki_page.created_at)}
- ago)
- %td= link_to_member(@project, wiki_page.user)
diff --git a/app/views/wikis/show.html.haml b/app/views/wikis/show.html.haml
deleted file mode 100644
index 7ff8b5cc01e..00000000000
--- a/app/views/wikis/show.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-%h3.page_title
- = @wiki.title
- %span.pull-right
- = link_to pages_project_wikis_path(@project), class: "btn btn-small grouped" do
- Pages
- - if can? current_user, :write_wiki, @project
- = link_to history_project_wiki_path(@project, @wiki), class: "btn btn-small grouped" do
- History
- = link_to edit_project_wiki_path(@project, @wiki), class: "btn btn-small grouped" do
- %i.icon-edit
- Edit
-%br
-- if @wiki != @most_recent_wiki
- .warning_message
- This is an old version of this page.
- You can view the #{link_to "most recent version", project_wiki_path(@project, @wiki)} or browse the #{link_to "history", history_project_wiki_path(@project, @wiki)}.
-
-.file_holder
- .file_content.wiki
- = preserve do
- = markdown @wiki.content
-
-%p.time Last edited by #{link_to_member @project, @wiki.user}, #{time_ago_in_words @wiki.created_at} ago
diff --git a/app/workers/gitlab_shell_worker.rb b/app/workers/gitlab_shell_worker.rb
index 0a921b1bd44..cfeda88bbc5 100644
--- a/app/workers/gitlab_shell_worker.rb
+++ b/app/workers/gitlab_shell_worker.rb
@@ -1,6 +1,6 @@
class GitlabShellWorker
include Sidekiq::Worker
- include Gitolited
+ include Gitlab::ShellAdapter
sidekiq_options queue: :gitlab_shell
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 3ef6d5977cf..6416aa608ec 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -1,5 +1,6 @@
class PostReceive
include Sidekiq::Worker
+ include Gitlab::Identifier
sidekiq_options queue: :post_receive
@@ -8,7 +9,7 @@ class PostReceive
if repo_path.start_with?(Gitlab.config.gitlab_shell.repos_path.to_s)
repo_path.gsub!(Gitlab.config.gitlab_shell.repos_path.to_s, "")
else
- Gitlab::GitLogger.error("POST-RECEIVE: Check gitlab.yml config for correct gitlab_shell.repos_path variable. \"#{Gitlab.config.gitlab_shell.repos_path}\" does not match \"#{repo_path}\"")
+ log("Check gitlab.yml config for correct gitlab_shell.repos_path variable. \"#{Gitlab.config.gitlab_shell.repos_path}\" does not match \"#{repo_path}\"")
end
repo_path.gsub!(/.git$/, "")
@@ -17,31 +18,21 @@ class PostReceive
project = Project.find_with_namespace(repo_path)
if project.nil?
- Gitlab::GitLogger.error("POST-RECEIVE: Triggered hook for non-existing project with full path \"#{repo_path} \"")
+ log("Triggered hook for non-existing project with full path \"#{repo_path} \"")
return false
end
- user = if identifier.blank?
- # Local push from gitlab
- email = project.repository.commit(newrev).author.email rescue nil
- User.find_by_email(email) if email
-
- elsif identifier =~ /\Auser-\d+\Z/
- # git push over http
- user_id = identifier.gsub("user-", "")
- User.find_by_id(user_id)
-
- elsif identifier =~ /\Akey-\d+\Z/
- # git push over ssh
- key_id = identifier.gsub("key-", "")
- Key.find_by_id(key_id).try(:user)
- end
+ user = identify(identifier, project, newrev)
unless user
- Gitlab::GitLogger.error("POST-RECEIVE: Triggered hook for non-existing user \"#{identifier} \"")
+ log("Triggered hook for non-existing user \"#{identifier} \"")
return false
end
- project.trigger_post_receive(oldrev, newrev, ref, user)
+ GitPushService.new.execute(project, user, oldrev, newrev, ref)
+ end
+
+ def log(message)
+ Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
end
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
new file mode 100644
index 00000000000..a3b4bd0c9b5
--- /dev/null
+++ b/app/workers/repository_import_worker.rb
@@ -0,0 +1,22 @@
+class RepositoryImportWorker
+ include Sidekiq::Worker
+ include Gitlab::ShellAdapter
+
+ sidekiq_options queue: :gitlab_shell
+
+ def perform(project_id)
+ project = Project.find(project_id)
+ result = gitlab_shell.send(:import_repository,
+ project.path_with_namespace,
+ project.import_url)
+
+ if result
+ project.imported = true
+ project.save
+ project.satellite.create unless project.satellite.exists?
+ project.discover_default_branch
+ else
+ project.imported = false
+ end
+ end
+end
diff --git a/config/application.rb b/config/application.rb
index d71de88ebe3..8ac07ef337a 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -4,7 +4,7 @@ require 'rails/all'
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
- # Bundler.require(*Rails.groups(:assets => %w(development test)))
+ # Bundler.require(*Rails.groups(assets: %w(development test)))
# If you want your assets lazily compiled in production, use this line
Bundler.require(:default, :assets, Rails.env)
end
@@ -24,6 +24,7 @@ module Gitlab
# Activate observers that should always be running.
config.active_record.observers = :activity_observer,
+ :project_activity_cache_observer,
:issue_observer,
:key_observer,
:merge_request_observer,
@@ -31,6 +32,7 @@ module Gitlab
:project_observer,
:system_hook_observer,
:user_observer,
+ :users_group_observer,
:users_project_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
@@ -66,5 +68,14 @@ module Gitlab
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
+
+ # Uncomment and customize the last line to run in a non-root path
+ # WARNING: This feature is no longer supported
+ # Note that three settings need to be changed for this to work.
+ # 1) In your application.rb file: config.relative_url_root = "/gitlab"
+ # 2) In your gitlab.yml file: relative_url_root: /gitlab
+ # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT']
+ #
+ # config.relative_url_root = "/gitlab"
end
end
diff --git a/config/aws.yml.example b/config/aws.yml.example
new file mode 100644
index 00000000000..29d029b078d
--- /dev/null
+++ b/config/aws.yml.example
@@ -0,0 +1,19 @@
+# See https://github.com/jnicklas/carrierwave#using-amazon-s3
+# for more options
+production:
+ access_key_id: AKIA1111111111111UA
+ secret_access_key: secret
+ bucket: mygitlab.production.us
+ region: us-east-1
+
+development:
+ access_key_id: AKIA1111111111111UA
+ secret_access_key: secret
+ bucket: mygitlab.development.us
+ region: us-east-1
+
+test:
+ access_key_id: AKIA1111111111111UA
+ secret_access_key: secret
+ bucket: mygitlab.test.us
+ region: us-east-1
diff --git a/config/database.yml.mysql b/config/database.yml.mysql
index 436bea77f0f..a3eff1a74f8 100644
--- a/config/database.yml.mysql
+++ b/config/database.yml.mysql
@@ -6,7 +6,7 @@ production:
encoding: utf8
reconnect: false
database: gitlabhq_production
- pool: 5
+ pool: 10
username: root
password: "secure password"
# host: localhost
diff --git a/config/database.yml.postgresql b/config/database.yml.postgresql
index 2bc0884f099..4b74f3348f8 100644
--- a/config/database.yml.postgresql
+++ b/config/database.yml.postgresql
@@ -5,8 +5,8 @@ production:
adapter: postgresql
encoding: unicode
database: gitlabhq_production
- pool: 5
- username: gitlab
+ pool: 10
+ username: git
password:
# host: localhost
# port: 5432
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 52fb8877cf3..e3476be8fba 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -40,7 +40,14 @@ Gitlab::Application.configure do
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
# Use a different cache store in production
- config.cache_store = :memory_store
+ config_file = Rails.root.join('config', 'resque.yml')
+
+ resque_url = if File.exists?(config_file)
+ YAML.load_file(config_file)[Rails.env]
+ else
+ "redis://localhost:6379"
+ end
+ config.cache_store = :redis_store, resque_url
# Enable serving of images, stylesheets, and JavaScripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
@@ -52,7 +59,7 @@ Gitlab::Application.configure do
# config.action_mailer.raise_delivery_errors = false
# Enable threaded mode
- # config.threadsafe!
+ # config.threadsafe! unless $rails_rake_task
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found)
@@ -68,8 +75,8 @@ Gitlab::Application.configure do
config.action_mailer.delivery_method = :sendmail
# Defaults to:
# # config.action_mailer.sendmail_settings = {
- # # :location => '/usr/sbin/sendmail',
- # # :arguments => '-i -t'
+ # # location: '/usr/sbin/sendmail',
+ # # arguments: '-i -t'
# # }
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 44154456430..c1cc9f872f3 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -1,5 +1,5 @@
-# # # # # # # # # # # # # # # # # #
-# Gitlab application config file #
+# # # # # # # # # # # # # # # # # #
+# GitLab application config file #
# # # # # # # # # # # # # # # # # #
#
# How to use:
@@ -18,8 +18,14 @@ production: &base
host: localhost
port: 80
https: false
- # Uncomment and customize to run in non-root path
- # Note that ENV['RAILS_RELATIVE_URL_ROOT'] in config/unicorn.rb may need to be changed
+
+ # Uncomment and customize the last line to run in a non-root path
+ # WARNING: This feature is no longer supported
+ # Note that three settings need to be changed for this to work.
+ # 1) In your application.rb file: config.relative_url_root = "/gitlab"
+ # 2) In your gitlab.yml file: relative_url_root: /gitlab
+ # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT']
+ #
# relative_url_root: /gitlab
# Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
@@ -32,18 +38,69 @@ production: &base
# Email address of your support contact (default: same as email_from)
support_email: support@localhost
- ## Project settings
+ ## User settings
default_projects_limit: 10
+ # default_can_create_group: false # default: true
+ # username_changing_enabled: false # default: true - User can change her username/namespace
+ ## Default theme
+ ## BASIC = 1
+ ## MARS = 2
+ ## MODERN = 3
+ ## GRAY = 4
+ ## COLOR = 5
+ # default_theme: 2 # default: 2
+
+
+ ## Users management
# signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled.
+ ## Automatic issue closing
+ # If a commit message matches this regular express, all issues referenced from the matched text will be closed
+ # if it's pushed to a project's default branch.
+ # issue_closing_pattern: ^([Cc]loses|[Ff]ixes) +#\d+
+
+ ## Default project features settings
+ default_projects_features:
+ issues: true
+ merge_requests: true
+ wiki: true
+ wall: false
+ snippets: false
+ public: false
+
+ ## External issues trackers
+ issues_tracker:
+ # redmine:
+ # ## If not nil, link 'Issues' on project page will be replaced with this
+ # ## Use placeholders:
+ # ## :project_id - GitLab project identifier
+ # ## :issues_tracker_id - Project Name or Id in external issue tracker
+ # project_url: "http://redmine.sample/projects/:issues_tracker_id"
+ #
+ # ## If not nil, links from /#\d/ entities from commit messages will replaced with this
+ # ## Use placeholders:
+ # ## :project_id - GitLab project identifier
+ # ## :issues_tracker_id - Project Name or Id in external issue tracker
+ # ## :id - Issue id (from commit messages)
+ # issues_url: "http://redmine.sample/issues/:id"
+ #
+ # ## If not nil, linkis to creating new issues will be replaced with this
+ # ## Use placeholders:
+ # ## :project_id - GitLab project identifier
+ # ## :issues_tracker_id - Project Name or Id in external issue tracker
+ # new_issue_url: "http://redmine.sample/projects/:issues_tracker_id/issues/new"
+ #
+ # jira:
+ # project_url: "http://jira.sample/issues/?jql=project=:issues_tracker_id"
+ # issues_url: "http://jira.sample/browse/:id"
+ # new_issue_url: "http://jira.sample/secure/CreateIssue.jspa"
+
## Gravatar
gravatar:
- enabled: true # Use user avatar images from Gravatar.com (default: true)
+ enabled: true # Use user avatar image from Gravatar.com (default: true)
# plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=mm
# ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm
-
-
#
# 2. Auth settings
# ==========================
@@ -58,23 +115,23 @@ production: &base
method: 'ssl' # "ssl" or "plain"
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
+ allow_username_or_email_login: true
- ## Omniauth settings
+ ## OmniAuth settings
omniauth:
- # Enable ability for users
- # Allow logging in via Twitter, Google, etc. using Omniauth providers
+ # Allow login via Twitter, Google, etc. using OmniAuth providers
enabled: false
# CAUTION!
- # This allows users to login without having a user account first (default: false)
+ # This allows users to login without having a user account first (default: false).
# User accounts will be created automatically when authentication was successful.
allow_single_sign_on: false
- # Locks down those users until they have been cleared by the admin (default: true)
+ # Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
## Auth providers
- # Uncomment the lines and fill in the data of the auth provider you want to use
- # If your favorite auth provider is not listed you can user others:
+ # Uncomment the following lines and fill in the data of the auth provider you want to use
+ # If your favorite auth provider is not listed you can use others:
# see https://github.com/gitlabhq/gitlabhq/wiki/Using-Custom-Omniauth-Providers
# The 'app_id' and 'app_secret' parameters are always passed as the first two
# arguments, followed by optional 'args' which can be either a hash or an array.
@@ -113,7 +170,7 @@ production: &base
upload_pack: true
receive_pack: true
- # If you use non-standart ssh port you need to specify it
+ # If you use non-standard ssh port you need to specify it
# ssh_port: 22
## Git settings
@@ -121,17 +178,35 @@ production: &base
# Use the default values unless you really know what you are doing
git:
bin_path: /usr/bin/git
- # Max size of git object like commit, in bytes
- # This value can be increased if you have a very large commits
+ # Max size of a git object (e.g. a commit), in bytes
+ # This value can be increased if you have very large commits
max_size: 5242880 # 5.megabytes
- # Git timeout to read commit, in seconds
+ # Git timeout to read a commit, in seconds
timeout: 10
+ #
+ # 4. Extra customization
+ # ==========================
+
+ extra:
+ ## Google analytics. Uncomment if you want it
+ # google_analytics_id: '_your_tracking_id'
+
+ ## Text under sign-in page (Markdown enabled)
+ # sign_in_text: |
+ # ![Company Logo](http://www.companydomain.com/logo.png)
+ # [Learn more about CompanyName](http://www.companydomain.com/)
+
development:
<<: *base
test:
<<: *base
+ issues_tracker:
+ redmine:
+ project_url: "http://redmine/projects/:issues_tracker_id"
+ issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
+ new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new"
staging:
<<: *base
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index b3fba99ebf3..1c8758d9420 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -37,26 +37,46 @@ end
# Default settings
Settings['ldap'] ||= Settingslogic.new({})
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
+Settings.ldap['allow_username_or_email_login'] = false if Settings.ldap['allow_username_or_email_login'].nil?
+
Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
Settings.omniauth['providers'] ||= []
+Settings['issues_tracker'] ||= {}
+
#
# GitLab
#
Settings['gitlab'] ||= Settingslogic.new({})
-Settings.gitlab['default_projects_limit'] ||= 10
+Settings.gitlab['default_projects_limit'] ||= 10
+Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
+Settings.gitlab['default_theme'] = Gitlab::Theme::MARS if Settings.gitlab['default_theme'].nil?
Settings.gitlab['host'] ||= 'localhost'
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
-Settings.gitlab['relative_url_root'] ||= ''
+Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}"
Settings.gitlab['support_email'] ||= Settings.gitlab.email_from
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git'
+Settings.gitlab['user_home'] ||= begin
+ Etc.getpwnam(Settings.gitlab['user']).dir
+rescue ArgumentError # no user configured
+ '/home/' + Settings.gitlab['user']
+end
Settings.gitlab['signup_enabled'] ||= false
+Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
+Settings.gitlab['issue_closing_pattern'] = '^([Cc]loses|[Ff]ixes) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
+Settings.gitlab['default_projects_features'] ||= {}
+Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
+Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
+Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
+Settings.gitlab.default_projects_features['wall'] = false if Settings.gitlab.default_projects_features['wall'].nil?
+Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
+Settings.gitlab.default_projects_features['public'] = false if Settings.gitlab.default_projects_features['public'].nil?
#
# Gravatar
@@ -70,10 +90,10 @@ Settings.gravatar['ssl_url'] ||= 'https://secure.gravatar.com/avatar/%{hash}?
# GitLab Shell
#
Settings['gitlab_shell'] ||= Settingslogic.new({})
-Settings.gitlab_shell['hooks_path'] ||= '/home/git/gitlab-shell/hooks/'
+Settings.gitlab_shell['hooks_path'] ||= Settings.gitlab['user_home'] + '/gitlab-shell/hooks/'
Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil?
Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil?
-Settings.gitlab_shell['repos_path'] ||= '/home/git/repositories/'
+Settings.gitlab_shell['repos_path'] ||= Settings.gitlab['user_home'] + '/repositories/'
Settings.gitlab_shell['ssh_host'] ||= (Settings.gitlab.host || 'localhost')
Settings.gitlab_shell['ssh_port'] ||= 22
Settings.gitlab_shell['ssh_user'] ||= Settings.gitlab.user
@@ -97,3 +117,17 @@ Settings.git['timeout'] ||= 10
Settings['satellites'] ||= Settingslogic.new({})
Settings.satellites['path'] = File.expand_path(Settings.satellites['path'] || "tmp/repo_satellites/", Rails.root)
+
+#
+# Extra customization
+#
+Settings['extra'] ||= Settingslogic.new({})
+
+#
+# Testing settings
+#
+if Rails.env.test?
+ Settings.gitlab['default_projects_limit'] = 42
+ Settings.gitlab['default_can_create_group'] = false
+ Settings.gitlab['default_can_create_team'] = false
+end
diff --git a/config/initializers/2_app.rb b/config/initializers/2_app.rb
index 748f15a11d9..e2f98002347 100644
--- a/config/initializers/2_app.rb
+++ b/config/initializers/2_app.rb
@@ -1,8 +1,13 @@
module Gitlab
- Version = File.read(Rails.root.join("VERSION"))
- Revision = `git log --pretty=format:'%h' -n 1`
+ VERSION = File.read(Rails.root.join("VERSION")).strip
+ REVISION = `git log --pretty=format:'%h' -n 1`
def self.config
Settings
end
end
+
+#
+# Load all libs for threadsafety
+#
+Dir["#{Rails.root}/lib/**/*.rb"].each { |file| require file }
diff --git a/config/initializers/3_grit_ext.rb b/config/initializers/3_grit_ext.rb
index 097c301a06a..8b298e821e7 100644
--- a/config/initializers/3_grit_ext.rb
+++ b/config/initializers/3_grit_ext.rb
@@ -1,9 +1,6 @@
require 'grit'
require 'pygments'
+Grit::Git.git_binary = Gitlab.config.git.bin_path
Grit::Git.git_timeout = Gitlab.config.git.timeout
Grit::Git.git_max_size = Gitlab.config.git.max_size
-
-Grit::Blob.class_eval do
- include Linguist::BlobHelper
-end
diff --git a/config/initializers/4_sidekiq.rb b/config/initializers/4_sidekiq.rb
index 6abe6e74fa0..c90d376273d 100644
--- a/config/initializers/4_sidekiq.rb
+++ b/config/initializers/4_sidekiq.rb
@@ -4,19 +4,19 @@ config_file = Rails.root.join('config', 'resque.yml')
resque_url = if File.exists?(config_file)
YAML.load_file(config_file)[Rails.env]
else
- "localhost:6379"
+ "redis://localhost:6379"
end
Sidekiq.configure_server do |config|
config.redis = {
- url: "redis://#{resque_url}",
+ url: resque_url,
namespace: 'resque:gitlab'
}
end
Sidekiq.configure_client do |config|
config.redis = {
- url: "redis://#{resque_url}",
+ url: resque_url,
namespace: 'resque:gitlab'
}
end
diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb
index 73436608c93..e60d9559c94 100644
--- a/config/initializers/5_backend.rb
+++ b/config/initializers/5_backend.rb
@@ -3,3 +3,9 @@ require Rails.root.join("lib", "gitlab", "backend", "grack_auth")
# GIT over SSH
require Rails.root.join("lib", "gitlab", "backend", "shell")
+
+# GitLab shell adapter
+require Rails.root.join("lib", "gitlab", "backend", "shell_adapter")
+
+# Gitlab Git repos path
+Gitlab::Git::Repository.repos_path = Gitlab.config.gitlab_shell.repos_path
diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb
index 9bb62f6d55a..45bc68f3220 100644
--- a/config/initializers/carrierwave.rb
+++ b/config/initializers/carrierwave.rb
@@ -1 +1,19 @@
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
+
+aws_file = Rails.root.join('config', 'aws.yml')
+
+if File.exists?(aws_file)
+ AWS_CONFIG = YAML.load(File.read(aws_file))[Rails.env]
+
+ CarrierWave.configure do |config|
+ config.fog_credentials = {
+ provider: 'AWS', # required
+ aws_access_key_id: AWS_CONFIG['access_key_id'], # required
+ aws_secret_access_key: AWS_CONFIG['secret_access_key'], # required
+ region: AWS_CONFIG['region'], # optional, defaults to 'us-east-1'
+ }
+ config.fog_directory = AWS_CONFIG['bucket'] # required
+ config.fog_public = false # optional, defaults to true
+ config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {}
+ end
+end
diff --git a/config/initializers/connection_fix.rb b/config/initializers/connection_fix.rb
deleted file mode 100644
index 16cb69ca68b..00000000000
--- a/config/initializers/connection_fix.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# from http://gist.github.com/238999
-#
-# If your workers are inactive for a long period of time, they'll lose
-# their MySQL connection.
-#
-# This hack ensures we re-connect whenever a connection is
-# lost. Because, really. why not?
-#
-# Stick this in RAILS_ROOT/config/initializers/connection_fix.rb (or somewhere similar)
-#
-# From:
-# http://coderrr.wordpress.com/2009/01/08/activerecord-threading-issues-and-resolutions/
-
-if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
-
- module ActiveRecord::ConnectionAdapters
-
- class Mysql2Adapter
- alias_method :execute_without_retry, :execute
-
- def execute(*args)
- execute_without_retry(*args)
- rescue ActiveRecord::StatementInvalid => e
- if e.message =~ /server has gone away/i
- warn "Server timed out, retrying"
- reconnect!
- retry
- else
- raise e
- end
- end
- end
-
- end
-
-end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 97946c54b40..39c1b7c235b 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -23,7 +23,7 @@ Devise.setup do |config|
# session. If you need permissions, you should implement that in a before filter.
# You can also supply a hash where the value is a boolean determining whether
# or not authentication should be aborted when the value is not present.
- # config.authentication_keys = [ :email ]
+ config.authentication_keys = [ :login ]
# Configure parameters from the request object used for authentication. Each entry
# given should be a request method and it will automatically be passed to the
@@ -94,12 +94,12 @@ Devise.setup do |config|
# config.extend_remember_period = false
# Options to be passed to the created cookie. For instance, you can set
- # :secure => true in order to force SSL only cookies.
+ # secure: true in order to force SSL only cookies.
# config.cookie_options = {}
# ==> Configuration for :validatable
# Range for password length. Default is 6..128.
- # config.password_length = 6..128
+ config.password_length = 6..128
# Email regex used to validate email formats. It simply asserts that
# an one (and only one) @ exists in the given string. This is mainly
@@ -202,18 +202,25 @@ Devise.setup do |config|
# config.warden do |manager|
# manager.failure_app = AnotherApp
# manager.intercept_401 = false
- # manager.default_strategies(:scope => :user).unshift :some_external_strategy
+ # manager.default_strategies(scope: :user).unshift :some_external_strategy
# end
if Gitlab.config.ldap.enabled
+ if Gitlab.config.ldap.allow_username_or_email_login
+ email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')}
+ else
+ email_stripping_proc = ->(name) {name}
+ end
+
config.omniauth :ldap,
- :host => Gitlab.config.ldap['host'],
- :base => Gitlab.config.ldap['base'],
- :uid => Gitlab.config.ldap['uid'],
- :port => Gitlab.config.ldap['port'],
- :method => Gitlab.config.ldap['method'],
- :bind_dn => Gitlab.config.ldap['bind_dn'],
- :password => Gitlab.config.ldap['password']
+ host: Gitlab.config.ldap['host'],
+ base: Gitlab.config.ldap['base'],
+ uid: Gitlab.config.ldap['uid'],
+ port: Gitlab.config.ldap['port'],
+ method: Gitlab.config.ldap['method'],
+ bind_dn: Gitlab.config.ldap['bind_dn'],
+ password: Gitlab.config.ldap['password'],
+ name_proc: email_stripping_proc
end
Gitlab.config.omniauth.providers.each do |provider|
diff --git a/config/initializers/haml.rb b/config/initializers/haml.rb
new file mode 100644
index 00000000000..7e8ddb3716b
--- /dev/null
+++ b/config/initializers/haml.rb
@@ -0,0 +1 @@
+Haml::Template.options[:ugly] = true
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index 6d3a9f07787..16d1d4a9fdd 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -1,7 +1,23 @@
# Be sure to restart your server when you modify this file.
+require 'securerandom'
+
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-Gitlab::Application.config.secret_token = '0a38e9a40ca5d66d7002a6ade0ed0f8b71058c820163f66cf65d91521ab55255ff708b9909b138008a7f13d68fec575def1dc3ff7200cd72b065896315e0bed2'
+
+def find_secure_token
+ token_file = Rails.root.join('.secret')
+ if File.exist? token_file
+ # Use the existing token.
+ File.read(token_file).chomp
+ else
+ # Generate a new token of 64 random hexadecimal characters and store it in token_file.
+ token = SecureRandom.hex(64)
+ File.write(token_file, token)
+ token
+ end
+end
+
+Gitlab::Application.config.secret_token = find_secure_token
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index e777ae2b78d..52a099c3e16 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -2,7 +2,8 @@
Gitlab::Application.config.session_store :cookie_store, key: '_gitlab_session',
secure: Gitlab::Application.config.force_ssl,
- httponly: true
+ httponly: true,
+ path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample
new file mode 100644
index 00000000000..e62ad0f4b71
--- /dev/null
+++ b/config/initializers/smtp_settings.rb.sample
@@ -0,0 +1,18 @@
+# To enable smtp email delivery for your GitLab instance do next:
+# 1. Change config/environments/production.rb to use smtp
+# config.action_mailer.delivery_method = :smtp
+# 2. Rename this file to smtp_settings.rb
+# 3. Edit settings inside this file
+# 4. Restart GitLab instance
+#
+if Gitlab::Application.config.action_mailer.delivery_method == :smtp
+ ActionMailer::Base.smtp_settings = {
+ address: "email.server.com",
+ port: 456,
+ user_name: "smtp",
+ password: "123456",
+ domain: "gitlab.company.com",
+ authentication: :login,
+ enable_starttls_auto: true
+ }
+end
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index 3b763cf410d..275273a0b12 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -17,6 +17,7 @@ en:
unauthenticated: 'You need to sign in before continuing.'
unconfirmed: 'You have to confirm your account before continuing.'
locked: 'Your account is locked.'
+ not_found_in_database: 'Invalid email or password.'
invalid: 'Invalid email or password.'
invalid_token: 'Invalid authentication token.'
timeout: 'Your session expired, please sign in again to continue.'
diff --git a/config/resque.yml.example b/config/resque.yml.example
index cd3d487408a..3c7ad0e5778 100644
--- a/config/resque.yml.example
+++ b/config/resque.yml.example
@@ -1,3 +1,3 @@
-development: localhost:6379
-test: localhost:6379
-production: redis.example.com:6379
+development: redis://localhost:6379
+test: redis://localhost:6379
+production: redis://redis.example.com:6379
diff --git a/config/routes.rb b/config/routes.rb
index 88667db130e..9d47faa19d5 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,5 @@
require 'sidekiq/web'
+require 'api/api'
Gitlab::Application.routes.draw do
#
@@ -7,8 +8,8 @@ Gitlab::Application.routes.draw do
get 'search' => "search#show"
# API
- require 'api'
- mount Gitlab::API => '/api'
+ API::API.logger Rails.logger
+ mount API::API => '/api'
constraint = lambda { |request| request.env["warden"].authenticate? and request.env['warden'].user.admin? }
constraints constraint do
@@ -28,6 +29,7 @@ Gitlab::Application.routes.draw do
#
get 'help' => 'help#index'
get 'help/api' => 'help#api'
+ get 'help/api/:category' => 'help#api', as: 'help_api_file'
get 'help/markdown' => 'help#markdown'
get 'help/permissions' => 'help#permissions'
get 'help/public_access' => 'help#public_access'
@@ -36,6 +38,17 @@ Gitlab::Application.routes.draw do
get 'help/system_hooks' => 'help#system_hooks'
get 'help/web_hooks' => 'help#web_hooks'
get 'help/workflow' => 'help#workflow'
+ get 'help/shortcuts'
+
+ #
+ # Global snippets
+ #
+ resources :snippets do
+ member do
+ get "raw"
+ end
+ end
+ get "/s/:username" => "snippets#user_index", as: :user_snippets, constraints: { username: /.*/ }
#
# Public namespace
@@ -46,6 +59,11 @@ Gitlab::Application.routes.draw do
end
#
+ # Attachments serving
+ #
+ get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ }
+
+ #
# Admin Area
#
namespace :admin do
@@ -59,16 +77,7 @@ Gitlab::Application.routes.draw do
resources :groups, constraints: { id: /[^\/]+/ } do
member do
- put :project_update
put :project_teams_update
- delete :remove_project
- end
- end
-
- resources :teams, constraints: { id: /[^\/]+/ } do
- scope module: :teams do
- resources :members, only: [:edit, :update, :destroy, :new, :create]
- resources :projects, only: [:edit, :update, :destroy, :new, :create], constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }
end
end
@@ -77,18 +86,8 @@ Gitlab::Application.routes.draw do
end
resource :logs, only: [:show]
- resource :resque, controller: 'resque', only: [:show]
-
- resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create] do
- member do
- get :team
- put :team_update
- end
- scope module: :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
- resources :members, only: [:edit, :update, :destroy]
- end
- end
-
+ resource :background_jobs, controller: 'background_jobs', only: [:show]
+ resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show]
root to: "dashboard#index"
end
@@ -108,9 +107,19 @@ Gitlab::Application.routes.draw do
put :reset_private_token
put :update_username
end
+
+ scope module: :profiles do
+ resource :notifications, only: [:show, :update]
+ resource :password, only: [:new, :create]
+ resources :keys
+ resources :groups, only: [:index] do
+ member do
+ delete :leave
+ end
+ end
+ end
end
- resources :keys
match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }
@@ -118,7 +127,7 @@ Gitlab::Application.routes.draw do
#
# Dashboard Area
#
- resource :dashboard, controller: "dashboard" do
+ resource :dashboard, controller: "dashboard", only: [:show] do
member do
get :projects
get :issues
@@ -129,28 +138,14 @@ Gitlab::Application.routes.draw do
#
# Groups Area
#
- resources :groups, constraints: { id: /[^\/]+/ } do
+ resources :groups, constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} do
member do
get :issues
get :merge_requests
- get :search
- get :people
- post :team_members
+ get :members
end
- end
- #
- # Teams Area
- #
- resources :teams, constraints: { id: /[^\/]+/ } do
- member do
- get :issues
- get :merge_requests
- end
- scope module: :teams do
- resources :members, only: [:index, :new, :create, :edit, :update, :destroy]
- resources :projects, only: [:index, :new, :create, :edit, :update, :destroy], constraints: { id: /[a-zA-Z.0-9_\-\/]+/ }
- end
+ resources :users_groups, only: [:create, :update, :destroy]
end
resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create]
@@ -160,130 +155,150 @@ Gitlab::Application.routes.draw do
#
# Project Area
#
- resources :projects, constraints: { id: /[a-zA-Z.0-9_\-\/]+/ }, except: [:new, :create, :index], path: "/" do
+ resources :projects, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do
member do
- get "wall"
- get "files"
+ put :transfer
+ post :fork
+ get :autocomplete_sources
end
- resources :blob, only: [:show], constraints: {id: /.+/}
- resources :tree, only: [:show, :edit, :update], constraints: {id: /.+/}
- resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
- resources :commits, only: [:show], constraints: {id: /.+/}
- resources :compare, only: [:index, :create]
- resources :blame, only: [:show], constraints: {id: /.+/}
- resources :graph, only: [:show], constraints: {id: /.+/}
- match "/compare/:from...:to" => "compare#show", as: "compare",
- :via => [:get, :post], constraints: {from: /.+/, to: /.+/}
-
- resources :wikis, only: [:show, :edit, :destroy, :create] do
- collection do
- get :pages
- end
+ scope module: :projects do
+ resources :blob, only: [:show], constraints: {id: /.+/}
+ resources :raw, only: [:show], constraints: {id: /.+/}
+ resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
+ resources :edit_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'edit'
+ resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
+ resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
+ resources :compare, only: [:index, :create]
+ resources :blame, only: [:show], constraints: {id: /.+/}
+ resources :network, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
+ resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
+ match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
+
+ resources :snippets, constraints: {id: /\d+/} do
+ member do
+ get "raw"
+ end
+ end
- member do
- get "history"
- end
- end
+ resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-]+/} do
+ collection do
+ get :pages
+ put ':id' => 'wikis#update'
+ get :git_access
+ end
- resource :repository do
- member do
- get "branches"
- get "tags"
- get "stats"
- get "archive"
+ member do
+ get "history"
+ end
end
- end
- resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
- member do
- get :test
+ resource :wall, only: [:show], constraints: {id: /\d+/} do
+ member do
+ get 'notes'
+ end
end
- end
- resources :deploy_keys
- resources :protected_branches, only: [:index, :create, :destroy]
+ resource :repository, only: [:show] do
+ member do
+ get "stats"
+ get "archive"
+ end
+ end
- resources :refs, only: [] do
- collection do
- get "switch"
+ resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
+ member do
+ get :test
+ end
end
- member do
- # tree viewer logs
- get "logs_tree", constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }
- get "logs_tree/:path" => "refs#logs_tree",
- as: :logs_file,
- constraints: {
- id: /[a-zA-Z.0-9\/_\-]+/,
- path: /.*/
- }
+ resources :deploy_keys, constraints: {id: /\d+/} do
+ member do
+ put :enable
+ put :disable
+ end
end
- end
- resources :merge_requests, constraints: {id: /\d+/}, except: [:destroy] do
- member do
- get :diffs
- get :automerge
- get :automerge_check
- get :ci_status
+ resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do
+ collection do
+ get :recent
+ end
end
- collection do
- get :branch_from
- get :branch_to
+ resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }
+ resources :protected_branches, only: [:index, :create, :destroy], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }
+
+ resources :refs, only: [] do
+ collection do
+ get "switch"
+ end
+
+ member do
+ # tree viewer logs
+ get "logs_tree", constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }
+ get "logs_tree/:path" => "refs#logs_tree",
+ as: :logs_file,
+ constraints: {
+ id: /[a-zA-Z.0-9\/_\-#%+]+/,
+ path: /.*/
+ }
+ end
end
- end
- resources :snippets do
- member do
- get "raw"
+ resources :merge_requests, constraints: {id: /\d+/}, except: [:destroy] do
+ member do
+ get :diffs
+ get :automerge
+ get :automerge_check
+ get :ci_status
+ end
+
+ collection do
+ get :branch_from
+ get :branch_to
+ get :update_branches
+ end
end
- end
- resources :hooks, only: [:index, :create, :destroy] do
- member do
- get :test
+ resources :hooks, only: [:index, :create, :destroy], constraints: {id: /\d+/} do
+ member do
+ get :test
+ end
end
- end
+ resources :team, controller: 'team_members', only: [:index]
+ resources :milestones, except: [:destroy], constraints: {id: /\d+/}
- resources :team, controller: 'team_members', only: [:index]
- resources :milestones, except: [:destroy]
- resources :labels, only: [:index]
- resources :issues, except: [:destroy] do
- collection do
- post :sort
- post :bulk_update
- get :search
+ resources :labels, only: [:index] do
+ collection do
+ post :generate
+ end
end
- end
-
- resources :team_members do
- collection do
- # Used for import team
- # from another project
- get :import
- post :apply_import
+ resources :issues, constraints: {id: /\d+/}, except: [:destroy] do
+ collection do
+ post :bulk_update
+ end
end
- end
- scope module: :projects do
- resources :teams, only: [] do
+ resources :team_members, except: [:index, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do
collection do
- get :available
- post :assign
+
+ # Used for import team
+ # from another project
+ get :import
+ post :apply_import
end
+ end
+
+ resources :notes, only: [:index, :create, :destroy, :update], constraints: {id: /\d+/} do
member do
- delete :resign
+ delete :delete_attachment
end
- end
- end
- resources :notes, only: [:index, :create, :destroy] do
- collection do
- post :preview
+ collection do
+ post :preview
+ end
end
end
end
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index 123033486f4..5c933df073a 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -1,68 +1,102 @@
-# uncomment and customize to run in non-root path
-# note that config/gitlab.yml web path should also be changed
-# ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
+# Sample verbose configuration file for Unicorn (not Rack)
+#
+# This configuration file documents many features of Unicorn
+# that may not be needed for some applications. See
+# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
+# for a much simpler configuration file.
+#
+# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
+# documentation.
-app_dir = File.expand_path '../../', __FILE__
+# Use at least one worker per core if you're on a dedicated server,
+# more will usually help for _short_ waits on databases/caches.
worker_processes 2
-working_directory app_dir
-# Load app into the master before forking workers for super-fast
-# worker spawn times
-preload_app true
+# Since Unicorn is never exposed to outside clients, it does not need to
+# run on the standard HTTP port (80), there is no reason to start Unicorn
+# as root unless it's from system init scripts.
+# If running the master process as root and the workers as an unprivileged
+# user, do this to switch euid/egid in the workers (also chowns logs):
+# user "unprivileged_user", "unprivileged_group"
-# nuke workers after 30 seconds (60 is the default)
-timeout 30
+# Help ensure your application will always spawn in the symlinked
+# "current" directory that Capistrano sets up.
+working_directory "/home/git/gitlab" # available in 0.94.0+
-# listen on a Unix domain socket and/or a TCP port,
+# listen on both a Unix domain socket and a TCP port,
+# we use a shorter backlog for quicker failover when busy
+listen "/home/git/gitlab/tmp/sockets/gitlab.socket", :backlog => 64
+listen "127.0.0.1:8080", :tcp_nopush => true
-#listen 8080 # listen to port 8080 on all TCP interfaces
-#listen "127.0.0.1:8080" # listen to port 8080 on the loopback interface
-listen "#{app_dir}/tmp/sockets/gitlab.socket"
+# nuke workers after 30 seconds instead of 60 seconds (the default)
+timeout 30
-pid "#{app_dir}/tmp/pids/unicorn.pid"
-stderr_path "#{app_dir}/log/unicorn.stderr.log"
-stdout_path "#{app_dir}/log/unicorn.stdout.log"
+# feel free to point this anywhere accessible on the filesystem
+pid "/home/git/gitlab/tmp/pids/unicorn.pid"
-# http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
-if GC.respond_to?(:copy_on_write_friendly=)
+# By default, the Unicorn logger will write to stderr.
+# Additionally, some applications/frameworks log to stderr or stdout,
+# so prevent them from going to /dev/null when daemonized here:
+stderr_path "/home/git/gitlab/log/unicorn.stderr.log"
+stdout_path "/home/git/gitlab/log/unicorn.stdout.log"
+
+# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
+# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
+preload_app true
+GC.respond_to?(:copy_on_write_friendly=) and
GC.copy_on_write_friendly = true
-end
+# Enable this flag to have unicorn test client connections by writing the
+# beginning of the HTTP headers before calling the application. This
+# prevents calling the application for connections that have disconnected
+# while queued. This is only guaranteed to detect clients on the same
+# host unicorn runs on, and unlikely to detect disconnects even on a
+# fast LAN.
+check_client_connection false
before_fork do |server, worker|
# the following is highly recomended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
- defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
+ defined?(ActiveRecord::Base) and
+ ActiveRecord::Base.connection.disconnect!
- ##
- # When sent a USR2, Unicorn will suffix its pidfile with .oldbin and
- # immediately start loading up a new version of itself (loaded with a new
- # version of our app). When this new Unicorn is completely loaded
- # it will begin spawning workers. The first worker spawned will check to
- # see if an .oldbin pidfile exists. If so, this means we've just booted up
- # a new Unicorn and need to tell the old one that it can now die. To do so
- # we send it a QUIT.
+ # The following is only recommended for memory/DB-constrained
+ # installations. It is not needed if your system can house
+ # twice as many worker_processes as you have configured.
#
- # Using this method we get 0 downtime deploys.
-
+ # This allows a new master process to incrementally
+ # phase out the old master process with SIGTTOU to avoid a
+ # thundering herd (especially in the "preload_app false" case)
+ # when doing a transparent upgrade. The last worker spawned
+ # will then kill off the old master process with a SIGQUIT.
old_pid = "#{server.config[:pid]}.oldbin"
-
- if File.exists?(old_pid) && server.pid != old_pid
+ if old_pid != server.pid
begin
sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
Process.kill(sig, File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH
- # someone else did our job for us
end
end
+ #
+ # Throttle the master from forking too quickly by sleeping. Due
+ # to the implementation of standard Unix signal handlers, this
+ # helps (but does not completely) prevent identical, repeated signals
+ # from being lost when the receiving process is busy.
+ # sleep 1
end
after_fork do |server, worker|
- # Unicorn master loads the app then forks off workers - because of the way
- # Unix forking works, we need to make sure we aren't using any of the parent's
- # sockets, e.g. db connection
+ # per-process listener ports for debugging/admin/migrations
+ # addr = "127.0.0.1:#{9293 + worker.nr}"
+ # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
+
+ # the following is *required* for Rails + "preload_app true",
+ defined?(ActiveRecord::Base) and
+ ActiveRecord::Base.establish_connection
- defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
- # Redis and Memcached would go here but their connections are established
- # on demand, so the master never opens a socket
+ # if preload_app is true, then you may also want to check and
+ # restart any other shared sockets/descriptors such as Memcached,
+ # and Redis. TokyoCabinet file handles are safe to reuse
+ # between any number of forked children (assuming your kernel
+ # correctly implements pread()/pwrite() system calls)
end
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index fbe41e4d22d..ecea8211393 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -7,5 +7,7 @@ User.seed(:id, [
password: "5iveL!fe",
password_confirmation: "5iveL!fe",
admin: true,
+ projects_limit: 100,
+ theme_id: Gitlab::Theme::MARS
}
])
diff --git a/db/fixtures/development/02_source_code.rb b/db/fixtures/development/02_source_code.rb
deleted file mode 100644
index 4a9e5d0c258..00000000000
--- a/db/fixtures/development/02_source_code.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-root = Gitlab.config.gitolite.repos_path
-
-projects = [
- { path: 'underscore.git', git: 'https://github.com/documentcloud/underscore.git' },
- { path: 'diaspora.git', git: 'https://github.com/diaspora/diaspora.git' },
- { path: 'brightbox/brightbox-cli.git', git: 'https://github.com/brightbox/brightbox-cli.git' },
- { path: 'brightbox/puppet.git', git: 'https://github.com/brightbox/puppet.git' },
- { path: 'gitlab/gitlabhq.git', git: 'https://github.com/gitlabhq/gitlabhq.git' },
- { path: 'gitlab/gitlab-ci.git', git: 'https://github.com/gitlabhq/gitlab-ci.git' },
- { path: 'gitlab/gitlab-recipes.git', git: 'https://github.com/gitlabhq/gitlab-recipes.git' },
-]
-
-projects.each do |project|
- project_path = File.join(root, project[:path])
-
- if File.exists?(project_path)
- print '-'
- next
- end
-
- if system("/home/git/gitlab-shell/bin/gitlab-projects import-project #{project[:path]} #{project[:git]}")
- print '.'
- else
- print 'F'
- end
-end
-
-puts "OK".green
-
diff --git a/db/fixtures/development/03_group.rb b/db/fixtures/development/03_group.rb
deleted file mode 100644
index 01174a4b72a..00000000000
--- a/db/fixtures/development/03_group.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-Group.seed(:id, [
- { id: 99, name: "GitLab", path: 'gitlab', owner_id: 1 },
- { id: 100, name: "Brightbox", path: 'brightbox', owner_id: 1 },
- { id: 101, name: "KDE", path: 'kde', owner_id: 1 },
-])
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 9904c48e518..43178dee25d 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -1,20 +1,49 @@
-Project.seed(:id, [
+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',
+]
- # Global
- { id: 1, name: "Underscore.js", path: "underscore", creator_id: 1 },
- { id: 2, name: "Diaspora", path: "diaspora", creator_id: 1 },
+project_urls.each_with_index do |url, i|
+ group_path, project_path = url.split('/')[-2..-1]
- # Brightbox
- { id: 3, namespace_id: 100, name: "Brightbox CLI", path: "brightbox-cli", creator_id: 1 },
- { id: 4, namespace_id: 100, name: "Puppet", path: "puppet", creator_id: 1 },
+ group = Group.find_by_path(group_path)
- # KDE
- { id: 5, namespace_id: 101, name: "kdebase", path: "kdebase", creator_id: 1},
- { id: 6, namespace_id: 101, name: "kdelibs", path: "kdelibs", creator_id: 1},
- { id: 7, namespace_id: 101, name: "amarok", path: "amarok", creator_id: 1},
+ unless group
+ group = Group.new(
+ name: group_path.titleize,
+ path: group_path
+ )
+ group.owner = User.first
+ group.save
+ end
- # GitLab
- { id: 8, namespace_id: 99, name: "gitlabhq", path: "gitlabhq", creator_id: 1},
- { id: 9, namespace_id: 99, name: "gitlab-ci", path: "gitlab-ci", creator_id: 1},
- { id: 10, namespace_id: 99, name: "gitlab-recipes", path: "gitlab-recipes", creator_id: 1},
-])
+ project_path.gsub!(".git", "")
+
+ params = {
+ import_url: url,
+ namespace_id: group.id,
+ name: project_path.titleize
+ }
+
+ project = Projects::CreateContext.new(User.first, params).execute
+
+ if project.valid?
+ print '.'
+ else
+ puts project.errors.full_messages
+ print 'F'
+ end
+end
diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb
index abcb0259618..cbb3e636acc 100644
--- a/db/fixtures/development/05_users.rb
+++ b/db/fixtures/development/05_users.rb
@@ -1,5 +1,5 @@
Gitlab::Seeder.quiet do
- (2..300).each do |i|
+ (2..50).each do |i|
begin
User.seed(:id, [{
id: i,
diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb
index 9fbf21a02d7..a1e01879db5 100644
--- a/db/fixtures/development/06_teams.rb
+++ b/db/fixtures/development/06_teams.rb
@@ -1,22 +1,23 @@
-Gitlab::Seeder.quiet do
-
- (1..300).each do |i|
- # Random Project
- project = Project.scoped.sample
-
- # Random user
- user = User.not_in_project(project).sample
+ActiveRecord::Base.observers.disable :all
- next unless user
-
- UsersProject.seed(:id, [{
- id: i,
- project_id: project.id,
- user_id: user.id,
- project_access: UsersProject.access_roles.values.sample
- }])
+Gitlab::Seeder.quiet do
+ Group.all.each do |group|
+ User.all.sample(4).each do |user|
+ if group.add_users([user.id], UsersGroup.group_access_roles.values.sample)
+ print '.'
+ else
+ print 'F'
+ end
+ end
+ end
- print('.')
+ Project.all.each do |project|
+ User.all.sample(4).each do |user|
+ if project.team << [user, UsersProject.access_roles.values.sample]
+ print '.'
+ else
+ print 'F'
+ end
+ end
end
end
-puts "OK".green
diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb
index a77f619f995..6fe7e246770 100644
--- a/db/fixtures/development/07_milestones.rb
+++ b/db/fixtures/development/07_milestones.rb
@@ -1,13 +1,18 @@
Milestone.seed(:id, [
- { :id => 1, :project_id => 1, :title => 'v' + Faker::Address.zip_code },
- { :id => 2, :project_id => 1, :title => 'v' + Faker::Address.zip_code },
- { :id => 3, :project_id => 1, :title => 'v' + Faker::Address.zip_code },
- { :id => 4, :project_id => 2, :title => 'v' + Faker::Address.zip_code },
- { :id => 5, :project_id => 2, :title => 'v' + Faker::Address.zip_code },
+ { id: 1, project_id: 1, title: 'v' + Faker::Address.zip_code },
+ { id: 2, project_id: 1, title: 'v' + Faker::Address.zip_code },
+ { id: 3, project_id: 1, title: 'v' + Faker::Address.zip_code },
+ { id: 4, project_id: 2, title: 'v' + Faker::Address.zip_code },
+ { id: 5, project_id: 2, title: 'v' + Faker::Address.zip_code },
- { :id => 6, :project_id => 2, :title => 'v' + Faker::Address.zip_code },
- { :id => 7, :project_id => 2, :title => 'v' + Faker::Address.zip_code },
- { :id => 8, :project_id => 3, :title => 'v' + Faker::Address.zip_code },
- { :id => 9, :project_id => 3, :title => 'v' + Faker::Address.zip_code },
- { :id => 11, :project_id => 3, :title => 'v' + Faker::Address.zip_code },
+ { id: 6, project_id: 2, title: 'v' + Faker::Address.zip_code },
+ { id: 7, project_id: 2, title: 'v' + Faker::Address.zip_code },
+ { id: 8, project_id: 3, title: 'v' + Faker::Address.zip_code },
+ { id: 9, project_id: 3, title: 'v' + Faker::Address.zip_code },
+ { id: 11, project_id: 3, title: 'v' + Faker::Address.zip_code },
])
+
+Milestone.all.map do |ml|
+ ml.set_iid
+ ml.save
+end
diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb
index 8978db4742b..31ba77254a3 100644
--- a/db/fixtures/development/09_issues.rb
+++ b/db/fixtures/development/09_issues.rb
@@ -1,3 +1,5 @@
+ActiveRecord::Base.observers.disable :all
+
Gitlab::Seeder.quiet do
(1..300).each do |i|
# Random Project
@@ -9,17 +11,22 @@ Gitlab::Seeder.quiet do
next unless user
user_id = user.id
- IssueObserver.current_user = user
+ Thread.current[:current_user] = user
Issue.seed(:id, [{
id: i,
project_id: project.id,
author_id: user_id,
assignee_id: user_id,
- closed: [true, false].sample,
+ state: ['opened', 'closed'].sample,
milestone: project.milestones.sample,
title: Faker::Lorem.sentence(6)
}])
print('.')
end
+
+ Issue.all.map do |issue|
+ issue.set_iid
+ issue.save
+ end
end
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 9904b4a1505..1e61ea28636 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -1,5 +1,7 @@
+ActiveRecord::Base.observers.disable :all
+
Gitlab::Seeder.quiet do
- (1..300).each do |i|
+ (1..100).each do |i|
# Random Project
project = Project.all.sample
@@ -8,19 +10,37 @@ Gitlab::Seeder.quiet do
next unless user
+ next if project.empty_repo?
+
+ branches = project.repository.branch_names.sample(2)
+
+ next if branches.uniq.size < 2
+
user_id = user.id
- MergeRequestObserver.current_user = user
+ Thread.current[:current_user] = user
+
MergeRequest.seed(:id, [{
id: i,
- source_branch: 'master',
- target_branch: 'feature',
- project_id: project.id,
+ source_branch: branches.first,
+ target_branch: branches.last,
+ source_project_id: project.id,
+ target_project_id: project.id,
author_id: user_id,
assignee_id: user_id,
- closed: [true, false].sample,
milestone: project.milestones.sample,
title: Faker::Lorem.sentence(6)
}])
print('.')
end
end
+
+MergeRequest.all.map do |mr|
+ mr.set_iid
+ mr.save
+end
+
+puts 'Load diffs for Merge Requests (it will take some time)...'
+MergeRequest.all.each do |mr|
+ mr.reload_code
+ print '.'
+end
diff --git a/db/fixtures/development/11_keys.rb b/db/fixtures/development/11_keys.rb
index 8e4724c277c..4b53ff411f2 100644
--- a/db/fixtures/development/11_keys.rb
+++ b/db/fixtures/development/11_keys.rb
@@ -1,3 +1,4 @@
+ActiveRecord::Base.observers.enable :all
Gitlab::Seeder.quiet do
User.first(30).each_with_index do |user, i|
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
new file mode 100644
index 00000000000..4ca8afe294e
--- /dev/null
+++ b/db/fixtures/development/12_snippets.rb
@@ -0,0 +1,24 @@
+ActiveRecord::Base.observers.disable :all
+
+Gitlab::Seeder.quiet do
+ contents = [
+ `curl https://gist.github.com/randx/4275756/raw/da2f262920c96d1a970d48bf2e99147954b1f4bd/glus1204.sh`,
+ `curl https://gist.github.com/randx/3754594/raw/11026a295e6ef3a151c635707a3e1e8e15fc4725/gitlab_setup.sh `,
+ `curl https://gist.github.com/randx/3065552/raw/29fbd09f4605a5ea22a5a9095e35fd1938dea4d6/gistfile1.sh`,
+ ]
+
+ (1..50).each do |i|
+ user = User.all.sample
+
+ PersonalSnippet.seed(:id, [{
+ id: i,
+ author_id: user.id,
+ title: Faker::Lorem.sentence(3),
+ file_name: Faker::Internet.domain_word + '.sh',
+ private: [true, false].sample,
+ content: contents.sample,
+ }])
+ print('.')
+ end
+end
+
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index f119694d11d..1b77d94905d 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -3,7 +3,10 @@ admin = User.create(
name: "Administrator",
username: 'root',
password: "5iveL!fe",
- password_confirmation: "5iveL!fe"
+ password_confirmation: "5iveL!fe",
+ password_expires_at: Time.now,
+ theme_id: Gitlab::Theme::MARS
+
)
admin.projects_limit = 10000
diff --git a/db/fixtures/test/001_repo.rb b/db/fixtures/test/001_repo.rb
index 18fc37cde0c..281e3476df1 100644
--- a/db/fixtures/test/001_repo.rb
+++ b/db/fixtures/test/001_repo.rb
@@ -19,5 +19,18 @@ FileUtils.cd(REPO_PATH) do
# Remove the copy
FileUtils.rm(SEED_REPO)
end
+puts ' done.'
+print "Creating seed satellite..."
+
+SATELLITE_PATH = Rails.root.join('tmp', 'satellite')
+# Make directory
+FileUtils.mkdir_p(SATELLITE_PATH)
+# Clear any potential directory
+FileUtils.rm_rf("#{SATELLITE_PATH}/gitlabhq")
+# Chdir, clone from the seed
+FileUtils.cd(SATELLITE_PATH) do
+ # Clone the satellite
+ `git clone --quiet #{REPO_PATH}/gitlabhq #{SATELLITE_PATH}/gitlabhq`
+end
puts ' done.'
diff --git a/db/migrate/20130123114545_add_issues_tracker_to_project.rb b/db/migrate/20130123114545_add_issues_tracker_to_project.rb
new file mode 100644
index 00000000000..288d0f07c9a
--- /dev/null
+++ b/db/migrate/20130123114545_add_issues_tracker_to_project.rb
@@ -0,0 +1,5 @@
+class AddIssuesTrackerToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :issues_tracker, :string, default: :gitlab, null: false
+ end
+end
diff --git a/db/migrate/20130206084024_add_description_to_namsespace.rb b/db/migrate/20130206084024_add_description_to_namsespace.rb
new file mode 100644
index 00000000000..ef02e489d03
--- /dev/null
+++ b/db/migrate/20130206084024_add_description_to_namsespace.rb
@@ -0,0 +1,5 @@
+class AddDescriptionToNamsespace < ActiveRecord::Migration
+ def change
+ add_column :namespaces, :description, :string, default: '', null: false
+ end
+end
diff --git a/db/migrate/20130207104426_add_description_to_teams.rb b/db/migrate/20130207104426_add_description_to_teams.rb
new file mode 100644
index 00000000000..6d03777901c
--- /dev/null
+++ b/db/migrate/20130207104426_add_description_to_teams.rb
@@ -0,0 +1,5 @@
+class AddDescriptionToTeams < ActiveRecord::Migration
+ def change
+ add_column :user_teams, :description, :string, default: '', null: false
+ end
+end
diff --git a/db/migrate/20130211085435_add_issues_tracker_id_to_project.rb b/db/migrate/20130211085435_add_issues_tracker_id_to_project.rb
new file mode 100644
index 00000000000..71763d18aee
--- /dev/null
+++ b/db/migrate/20130211085435_add_issues_tracker_id_to_project.rb
@@ -0,0 +1,5 @@
+class AddIssuesTrackerIdToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :issues_tracker_id, :string
+ end
+end
diff --git a/db/migrate/20130214154045_rename_state_to_merge_status_in_milestone.rb b/db/migrate/20130214154045_rename_state_to_merge_status_in_milestone.rb
new file mode 100644
index 00000000000..23797fe1894
--- /dev/null
+++ b/db/migrate/20130214154045_rename_state_to_merge_status_in_milestone.rb
@@ -0,0 +1,5 @@
+class RenameStateToMergeStatusInMilestone < ActiveRecord::Migration
+ def change
+ rename_column :merge_requests, :state, :merge_status
+ end
+end
diff --git a/db/migrate/20130218140952_add_state_to_issue.rb b/db/migrate/20130218140952_add_state_to_issue.rb
new file mode 100644
index 00000000000..062103d0e33
--- /dev/null
+++ b/db/migrate/20130218140952_add_state_to_issue.rb
@@ -0,0 +1,5 @@
+class AddStateToIssue < ActiveRecord::Migration
+ def change
+ add_column :issues, :state, :string
+ end
+end
diff --git a/db/migrate/20130218141038_add_state_to_merge_request.rb b/db/migrate/20130218141038_add_state_to_merge_request.rb
new file mode 100644
index 00000000000..ac4108ee311
--- /dev/null
+++ b/db/migrate/20130218141038_add_state_to_merge_request.rb
@@ -0,0 +1,5 @@
+class AddStateToMergeRequest < ActiveRecord::Migration
+ def change
+ add_column :merge_requests, :state, :string
+ end
+end
diff --git a/db/migrate/20130218141117_add_state_to_milestone.rb b/db/migrate/20130218141117_add_state_to_milestone.rb
new file mode 100644
index 00000000000..c84039106bd
--- /dev/null
+++ b/db/migrate/20130218141117_add_state_to_milestone.rb
@@ -0,0 +1,5 @@
+class AddStateToMilestone < ActiveRecord::Migration
+ def change
+ add_column :milestones, :state, :string
+ end
+end
diff --git a/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb b/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
new file mode 100644
index 00000000000..9fa96203ffd
--- /dev/null
+++ b/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
@@ -0,0 +1,14 @@
+class ConvertClosedToStateInIssue < ActiveRecord::Migration
+ def up
+ Issue.transaction do
+ Issue.where(closed: true).update_all(state: :closed)
+ Issue.where(closed: false).update_all(state: :opened)
+ end
+ end
+
+ def down
+ Issue.transaction do
+ Issue.where(state: :closed).update_all(closed: true)
+ end
+ end
+end
diff --git a/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb b/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
new file mode 100644
index 00000000000..ebb7ae585e6
--- /dev/null
+++ b/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
@@ -0,0 +1,16 @@
+class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration
+ def up
+ MergeRequest.transaction do
+ MergeRequest.where(closed: true, merged: true).update_all(state: :merged)
+ MergeRequest.where(closed: true, merged: false).update_all(state: :closed)
+ MergeRequest.where(closed: false).update_all(state: :opened)
+ end
+ end
+
+ def down
+ MergeRequest.transaction do
+ MergeRequest.where(state: :closed).update_all(closed: true)
+ MergeRequest.where(state: :merged).update_all(closed: true, merged: true)
+ end
+ end
+end
diff --git a/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb b/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
new file mode 100644
index 00000000000..1978ea89153
--- /dev/null
+++ b/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
@@ -0,0 +1,14 @@
+class ConvertClosedToStateInMilestone < ActiveRecord::Migration
+ def up
+ Milestone.transaction do
+ Milestone.where(closed: true).update_all(state: :closed)
+ Milestone.where(closed: false).update_all(state: :active)
+ end
+ end
+
+ def down
+ Milestone.transaction do
+ Milestone.where(state: :closed).update_all(closed: true)
+ end
+ end
+end
diff --git a/db/migrate/20130218141444_remove_merged_from_merge_request.rb b/db/migrate/20130218141444_remove_merged_from_merge_request.rb
new file mode 100644
index 00000000000..a7bd82f5000
--- /dev/null
+++ b/db/migrate/20130218141444_remove_merged_from_merge_request.rb
@@ -0,0 +1,9 @@
+class RemoveMergedFromMergeRequest < ActiveRecord::Migration
+ def up
+ remove_column :merge_requests, :merged
+ end
+
+ def down
+ add_column :merge_requests, :merged, :boolean, default: true, null: false
+ end
+end
diff --git a/db/migrate/20130218141507_remove_closed_from_issue.rb b/db/migrate/20130218141507_remove_closed_from_issue.rb
new file mode 100644
index 00000000000..95cc064252b
--- /dev/null
+++ b/db/migrate/20130218141507_remove_closed_from_issue.rb
@@ -0,0 +1,9 @@
+class RemoveClosedFromIssue < ActiveRecord::Migration
+ def up
+ remove_column :issues, :closed
+ end
+
+ def down
+ add_column :issues, :closed, :boolean
+ end
+end
diff --git a/db/migrate/20130218141536_remove_closed_from_merge_request.rb b/db/migrate/20130218141536_remove_closed_from_merge_request.rb
new file mode 100644
index 00000000000..371835938b2
--- /dev/null
+++ b/db/migrate/20130218141536_remove_closed_from_merge_request.rb
@@ -0,0 +1,9 @@
+class RemoveClosedFromMergeRequest < ActiveRecord::Migration
+ def up
+ remove_column :merge_requests, :closed
+ end
+
+ def down
+ add_column :merge_requests, :closed, :boolean
+ end
+end
diff --git a/db/migrate/20130218141554_remove_closed_from_milestone.rb b/db/migrate/20130218141554_remove_closed_from_milestone.rb
new file mode 100644
index 00000000000..e8dae4a19b1
--- /dev/null
+++ b/db/migrate/20130218141554_remove_closed_from_milestone.rb
@@ -0,0 +1,9 @@
+class RemoveClosedFromMilestone < ActiveRecord::Migration
+ def up
+ remove_column :milestones, :closed
+ end
+
+ def down
+ add_column :milestones, :closed, :boolean
+ end
+end
diff --git a/db/migrate/20130220124204_add_new_merge_status_to_merge_request.rb b/db/migrate/20130220124204_add_new_merge_status_to_merge_request.rb
new file mode 100644
index 00000000000..d78bd0ae923
--- /dev/null
+++ b/db/migrate/20130220124204_add_new_merge_status_to_merge_request.rb
@@ -0,0 +1,5 @@
+class AddNewMergeStatusToMergeRequest < ActiveRecord::Migration
+ def change
+ add_column :merge_requests, :new_merge_status, :string
+ end
+end
diff --git a/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb b/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb
new file mode 100644
index 00000000000..b310b35e373
--- /dev/null
+++ b/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb
@@ -0,0 +1,17 @@
+class ConvertMergeStatusInMergeRequest < ActiveRecord::Migration
+ def up
+ MergeRequest.transaction do
+ MergeRequest.where(merge_status: 1).update_all("new_merge_status = 'unchecked'")
+ MergeRequest.where(merge_status: 2).update_all("new_merge_status = 'can_be_merged'")
+ MergeRequest.where(merge_status: 3).update_all("new_merge_status = 'cannot_be_merged'")
+ end
+ end
+
+ def down
+ MergeRequest.transaction do
+ MergeRequest.where(new_merge_status: :unchecked).update_all("merge_status = 1")
+ MergeRequest.where(new_merge_status: :can_be_merged).update_all("merge_status = 2")
+ MergeRequest.where(new_merge_status: :cannot_be_merged).update_all("merge_status = 3")
+ end
+ end
+end
diff --git a/db/migrate/20130220125545_remove_merge_status_from_merge_request.rb b/db/migrate/20130220125545_remove_merge_status_from_merge_request.rb
new file mode 100644
index 00000000000..9083183beb0
--- /dev/null
+++ b/db/migrate/20130220125545_remove_merge_status_from_merge_request.rb
@@ -0,0 +1,9 @@
+class RemoveMergeStatusFromMergeRequest < ActiveRecord::Migration
+ def up
+ remove_column :merge_requests, :merge_status
+ end
+
+ def down
+ add_column :merge_requests, :merge_status, :integer
+ end
+end
diff --git a/db/migrate/20130220133245_rename_new_merge_status_to_merge_status_in_milestone.rb b/db/migrate/20130220133245_rename_new_merge_status_to_merge_status_in_milestone.rb
new file mode 100644
index 00000000000..3f8f38dc979
--- /dev/null
+++ b/db/migrate/20130220133245_rename_new_merge_status_to_merge_status_in_milestone.rb
@@ -0,0 +1,5 @@
+class RenameNewMergeStatusToMergeStatusInMilestone < ActiveRecord::Migration
+ def change
+ rename_column :merge_requests, :new_merge_status, :merge_status
+ end
+end
diff --git a/db/migrate/20130304104623_add_state_to_user.rb b/db/migrate/20130304104623_add_state_to_user.rb
new file mode 100644
index 00000000000..8154c21065f
--- /dev/null
+++ b/db/migrate/20130304104623_add_state_to_user.rb
@@ -0,0 +1,5 @@
+class AddStateToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :state, :string
+ end
+end
diff --git a/db/migrate/20130304104740_convert_blocked_to_state.rb b/db/migrate/20130304104740_convert_blocked_to_state.rb
new file mode 100644
index 00000000000..e8d5257ac96
--- /dev/null
+++ b/db/migrate/20130304104740_convert_blocked_to_state.rb
@@ -0,0 +1,14 @@
+class ConvertBlockedToState < ActiveRecord::Migration
+ def up
+ User.transaction do
+ User.where(blocked: true).update_all(state: :blocked)
+ User.where(blocked: false).update_all(state: :active)
+ end
+ end
+
+ def down
+ User.transaction do
+ User.where(state: :blocked).update_all(blocked: :true)
+ end
+ end
+end
diff --git a/db/migrate/20130304105317_remove_blocked_from_user.rb b/db/migrate/20130304105317_remove_blocked_from_user.rb
new file mode 100644
index 00000000000..e010474538c
--- /dev/null
+++ b/db/migrate/20130304105317_remove_blocked_from_user.rb
@@ -0,0 +1,9 @@
+class RemoveBlockedFromUser < ActiveRecord::Migration
+ def up
+ remove_column :users, :blocked
+ end
+
+ def down
+ add_column :users, :blocked, :boolean
+ end
+end
diff --git a/db/migrate/20130315124931_user_color_scheme.rb b/db/migrate/20130315124931_user_color_scheme.rb
new file mode 100644
index 00000000000..fe139e32ea7
--- /dev/null
+++ b/db/migrate/20130315124931_user_color_scheme.rb
@@ -0,0 +1,12 @@
+class UserColorScheme < ActiveRecord::Migration
+ def up
+ add_column :users, :color_scheme_id, :integer, null: false, default: 1
+ User.where(dark_scheme: true).update_all(color_scheme_id: 2)
+ remove_column :users, :dark_scheme
+ end
+
+ def down
+ add_column :users, :dark_scheme, :boolean, null: false, default: false
+ remove_column :users, :color_scheme_id
+ end
+end
diff --git a/db/migrate/20130318212250_add_snippets_to_features.rb b/db/migrate/20130318212250_add_snippets_to_features.rb
new file mode 100644
index 00000000000..ad0b4434c43
--- /dev/null
+++ b/db/migrate/20130318212250_add_snippets_to_features.rb
@@ -0,0 +1,5 @@
+class AddSnippetsToFeatures < ActiveRecord::Migration
+ def change
+ add_column :projects, :snippets_enabled, :boolean, null: false, default: true
+ end
+end
diff --git a/db/migrate/20130319214458_create_forked_project_links.rb b/db/migrate/20130319214458_create_forked_project_links.rb
new file mode 100644
index 00000000000..f91afc26e77
--- /dev/null
+++ b/db/migrate/20130319214458_create_forked_project_links.rb
@@ -0,0 +1,11 @@
+class CreateForkedProjectLinks < ActiveRecord::Migration
+ def change
+ create_table :forked_project_links do |t|
+ t.integer :forked_to_project_id, null: false
+ t.integer :forked_from_project_id, null: false
+
+ t.timestamps
+ end
+ add_index :forked_project_links, :forked_to_project_id, unique: true
+ end
+end
diff --git a/db/migrate/20130323174317_add_private_to_snippets.rb b/db/migrate/20130323174317_add_private_to_snippets.rb
new file mode 100644
index 00000000000..92f3a5c7011
--- /dev/null
+++ b/db/migrate/20130323174317_add_private_to_snippets.rb
@@ -0,0 +1,5 @@
+class AddPrivateToSnippets < ActiveRecord::Migration
+ def change
+ add_column :snippets, :private, :boolean, null: false, default: true
+ end
+end
diff --git a/db/migrate/20130324151736_add_type_to_snippets.rb b/db/migrate/20130324151736_add_type_to_snippets.rb
new file mode 100644
index 00000000000..276aab2ca15
--- /dev/null
+++ b/db/migrate/20130324151736_add_type_to_snippets.rb
@@ -0,0 +1,5 @@
+class AddTypeToSnippets < ActiveRecord::Migration
+ def change
+ add_column :snippets, :type, :string
+ end
+end
diff --git a/db/migrate/20130324172327_change_project_id_to_null_in_snipepts.rb b/db/migrate/20130324172327_change_project_id_to_null_in_snipepts.rb
new file mode 100644
index 00000000000..4c992bac4d1
--- /dev/null
+++ b/db/migrate/20130324172327_change_project_id_to_null_in_snipepts.rb
@@ -0,0 +1,9 @@
+class ChangeProjectIdToNullInSnipepts < ActiveRecord::Migration
+ def up
+ change_column :snippets, :project_id, :integer, :null => true
+ end
+
+ def down
+ change_column :snippets, :project_id, :integer, :null => false
+ end
+end
diff --git a/db/migrate/20130324203535_add_type_value_for_snippets.rb b/db/migrate/20130324203535_add_type_value_for_snippets.rb
new file mode 100644
index 00000000000..8c05dd2cc71
--- /dev/null
+++ b/db/migrate/20130324203535_add_type_value_for_snippets.rb
@@ -0,0 +1,8 @@
+class AddTypeValueForSnippets < ActiveRecord::Migration
+ def up
+ Snippet.where("project_id IS NOT NULL").update_all(type: 'ProjectSnippet')
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20130325173941_add_notification_level_to_user.rb b/db/migrate/20130325173941_add_notification_level_to_user.rb
new file mode 100644
index 00000000000..9f466e38c13
--- /dev/null
+++ b/db/migrate/20130325173941_add_notification_level_to_user.rb
@@ -0,0 +1,5 @@
+class AddNotificationLevelToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :notification_level, :integer, null: false, default: 1
+ end
+end
diff --git a/db/migrate/20130326142630_add_index_to_users_authentication_token.rb b/db/migrate/20130326142630_add_index_to_users_authentication_token.rb
new file mode 100644
index 00000000000..d42ef113738
--- /dev/null
+++ b/db/migrate/20130326142630_add_index_to_users_authentication_token.rb
@@ -0,0 +1,5 @@
+class AddIndexToUsersAuthenticationToken < ActiveRecord::Migration
+ def change
+ add_index :users, :authentication_token, unique: true
+ end
+end
diff --git a/db/migrate/20130403003950_add_last_activity_column_into_project.rb b/db/migrate/20130403003950_add_last_activity_column_into_project.rb
new file mode 100644
index 00000000000..2a036bd9993
--- /dev/null
+++ b/db/migrate/20130403003950_add_last_activity_column_into_project.rb
@@ -0,0 +1,21 @@
+class AddLastActivityColumnIntoProject < ActiveRecord::Migration
+ def up
+ add_column :projects, :last_activity_at, :datetime
+ add_index :projects, :last_activity_at
+
+ Project.find_each do |project|
+ last_activity_date = if project.last_activity
+ project.last_activity.created_at
+ else
+ project.updated_at
+ end
+
+ project.update_attribute(:last_activity_at, last_activity_date)
+ end
+ end
+
+ def down
+ remove_index :projects, :last_activity_at
+ remove_column :projects, :last_activity_at
+ end
+end
diff --git a/db/migrate/20130404164628_add_notification_level_to_user_project.rb b/db/migrate/20130404164628_add_notification_level_to_user_project.rb
new file mode 100644
index 00000000000..27de5d6bf55
--- /dev/null
+++ b/db/migrate/20130404164628_add_notification_level_to_user_project.rb
@@ -0,0 +1,5 @@
+class AddNotificationLevelToUserProject < ActiveRecord::Migration
+ def change
+ add_column :users_projects, :notification_level, :integer, null: false, default: 3
+ end
+end
diff --git a/db/migrate/20130410175022_remove_wiki_table.rb b/db/migrate/20130410175022_remove_wiki_table.rb
new file mode 100644
index 00000000000..9077aa2473c
--- /dev/null
+++ b/db/migrate/20130410175022_remove_wiki_table.rb
@@ -0,0 +1,9 @@
+class RemoveWikiTable < ActiveRecord::Migration
+ def up
+ drop_table :wikis
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/db/migrate/20130419190306_allow_merges_for_forks.rb b/db/migrate/20130419190306_allow_merges_for_forks.rb
new file mode 100644
index 00000000000..56ce58a846d
--- /dev/null
+++ b/db/migrate/20130419190306_allow_merges_for_forks.rb
@@ -0,0 +1,13 @@
+class AllowMergesForForks < ActiveRecord::Migration
+ def self.up
+ add_column :merge_requests, :target_project_id, :integer, :null => true
+ MergeRequest.update_all("target_project_id = project_id")
+ change_column :merge_requests, :target_project_id, :integer, :null => false
+ rename_column :merge_requests, :project_id, :source_project_id
+ end
+
+ def self.down
+ remove_column :merge_requests, :target_project_id
+ rename_column :merge_requests, :source_project_id,:project_id
+ end
+end
diff --git a/db/migrate/20130506085413_add_type_to_key.rb b/db/migrate/20130506085413_add_type_to_key.rb
new file mode 100644
index 00000000000..315e7ca77b3
--- /dev/null
+++ b/db/migrate/20130506085413_add_type_to_key.rb
@@ -0,0 +1,5 @@
+class AddTypeToKey < ActiveRecord::Migration
+ def change
+ add_column :keys, :type, :string
+ end
+end
diff --git a/db/migrate/20130506090604_create_deploy_keys_projects.rb b/db/migrate/20130506090604_create_deploy_keys_projects.rb
new file mode 100644
index 00000000000..0dc8cdeb07d
--- /dev/null
+++ b/db/migrate/20130506090604_create_deploy_keys_projects.rb
@@ -0,0 +1,10 @@
+class CreateDeployKeysProjects < ActiveRecord::Migration
+ def change
+ create_table :deploy_keys_projects do |t|
+ t.integer :deploy_key_id, null: false
+ t.integer :project_id, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20130506095501_remove_project_id_from_key.rb b/db/migrate/20130506095501_remove_project_id_from_key.rb
new file mode 100644
index 00000000000..4214fd45d14
--- /dev/null
+++ b/db/migrate/20130506095501_remove_project_id_from_key.rb
@@ -0,0 +1,22 @@
+class RemoveProjectIdFromKey < ActiveRecord::Migration
+ def up
+ puts 'Migrate deploy keys: '
+ Key.where('project_id IS NOT NULL').update_all(type: 'DeployKey')
+
+ DeployKey.all.each do |key|
+ project = Project.find_by_id(key.project_id)
+ if project
+ project.deploy_keys << key
+ print '.'
+ end
+ end
+
+ puts 'Done'
+
+ remove_column :keys, :project_id
+ end
+
+ def down
+ add_column :keys, :project_id, :integer
+ end
+end
diff --git a/db/migrate/20130522141856_add_more_fields_to_service.rb b/db/migrate/20130522141856_add_more_fields_to_service.rb
new file mode 100644
index 00000000000..298e902df2f
--- /dev/null
+++ b/db/migrate/20130522141856_add_more_fields_to_service.rb
@@ -0,0 +1,6 @@
+class AddMoreFieldsToService < ActiveRecord::Migration
+ def change
+ add_column :services, :subdomain, :string
+ add_column :services, :room, :string
+ end
+end
diff --git a/db/migrate/20130528184641_add_system_to_notes.rb b/db/migrate/20130528184641_add_system_to_notes.rb
new file mode 100644
index 00000000000..1b22a4934f9
--- /dev/null
+++ b/db/migrate/20130528184641_add_system_to_notes.rb
@@ -0,0 +1,16 @@
+class AddSystemToNotes < ActiveRecord::Migration
+ class Note < ActiveRecord::Base
+ end
+
+ def up
+ add_column :notes, :system, :boolean, default: false, null: false
+
+ Note.reset_column_information
+ Note.update_all(system: false)
+ Note.where("note like '_status changed to%'").update_all(system: true)
+ end
+
+ def down
+ remove_column :notes, :system
+ end
+end
diff --git a/db/migrate/20130611210815_increase_snippet_text_column_size.rb b/db/migrate/20130611210815_increase_snippet_text_column_size.rb
new file mode 100644
index 00000000000..f7b4447e43e
--- /dev/null
+++ b/db/migrate/20130611210815_increase_snippet_text_column_size.rb
@@ -0,0 +1,9 @@
+class IncreaseSnippetTextColumnSize < ActiveRecord::Migration
+ def up
+ # MYSQL LARGETEXT for snippet
+ change_column :snippets, :content, :text, :limit => 4294967295
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20130613165816_add_password_expires_at_to_users.rb b/db/migrate/20130613165816_add_password_expires_at_to_users.rb
new file mode 100644
index 00000000000..3479c8e64d0
--- /dev/null
+++ b/db/migrate/20130613165816_add_password_expires_at_to_users.rb
@@ -0,0 +1,5 @@
+class AddPasswordExpiresAtToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :password_expires_at, :datetime
+ end
+end
diff --git a/db/migrate/20130613173246_add_created_by_id_to_user.rb b/db/migrate/20130613173246_add_created_by_id_to_user.rb
new file mode 100644
index 00000000000..615e96eb156
--- /dev/null
+++ b/db/migrate/20130613173246_add_created_by_id_to_user.rb
@@ -0,0 +1,5 @@
+class AddCreatedByIdToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :created_by_id, :integer
+ end
+end
diff --git a/db/migrate/20130614132337_add_improted_to_project.rb b/db/migrate/20130614132337_add_improted_to_project.rb
new file mode 100644
index 00000000000..cc882c3f10a
--- /dev/null
+++ b/db/migrate/20130614132337_add_improted_to_project.rb
@@ -0,0 +1,5 @@
+class AddImprotedToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :imported, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20130617095603_create_users_groups.rb b/db/migrate/20130617095603_create_users_groups.rb
new file mode 100644
index 00000000000..2efc04f1151
--- /dev/null
+++ b/db/migrate/20130617095603_create_users_groups.rb
@@ -0,0 +1,11 @@
+class CreateUsersGroups < ActiveRecord::Migration
+ def change
+ create_table :users_groups do |t|
+ t.integer :group_access, null: false
+ t.integer :group_id, null: false
+ t.integer :user_id, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20130621195223_add_notification_level_to_user_group.rb b/db/migrate/20130621195223_add_notification_level_to_user_group.rb
new file mode 100644
index 00000000000..8c2e3dfcaca
--- /dev/null
+++ b/db/migrate/20130621195223_add_notification_level_to_user_group.rb
@@ -0,0 +1,5 @@
+class AddNotificationLevelToUserGroup < ActiveRecord::Migration
+ def change
+ add_column :users_groups, :notification_level, :integer, null: false, default: 3
+ end
+end
diff --git a/db/migrate/20130622115340_add_more_db_index.rb b/db/migrate/20130622115340_add_more_db_index.rb
new file mode 100644
index 00000000000..9570a7a3f1e
--- /dev/null
+++ b/db/migrate/20130622115340_add_more_db_index.rb
@@ -0,0 +1,12 @@
+class AddMoreDbIndex < ActiveRecord::Migration
+ def change
+ add_index :deploy_keys_projects, :project_id
+ add_index :web_hooks, :project_id
+ add_index :protected_branches, :project_id
+
+ add_index :users_groups, :user_id
+ add_index :snippets, :author_id
+ add_index :notes, :author_id
+ add_index :notes, [:noteable_id, :noteable_type]
+ end
+end
diff --git a/db/migrate/20130624162710_add_fingerprint_to_key.rb b/db/migrate/20130624162710_add_fingerprint_to_key.rb
new file mode 100644
index 00000000000..544a8366727
--- /dev/null
+++ b/db/migrate/20130624162710_add_fingerprint_to_key.rb
@@ -0,0 +1,6 @@
+class AddFingerprintToKey < ActiveRecord::Migration
+ def change
+ add_column :keys, :fingerprint, :string
+ remove_column :keys, :identifier
+ end
+end
diff --git a/db/migrate/20130804151314_add_st_diff_to_note.rb b/db/migrate/20130804151314_add_st_diff_to_note.rb
new file mode 100644
index 00000000000..3f9abb975c3
--- /dev/null
+++ b/db/migrate/20130804151314_add_st_diff_to_note.rb
@@ -0,0 +1,5 @@
+class AddStDiffToNote < ActiveRecord::Migration
+ def change
+ add_column :notes, :st_diff, :text, :null => true
+ end
+end
diff --git a/db/migrate/20130812143708_add_import_url_to_project.rb b/db/migrate/20130812143708_add_import_url_to_project.rb
new file mode 100644
index 00000000000..023a48741b2
--- /dev/null
+++ b/db/migrate/20130812143708_add_import_url_to_project.rb
@@ -0,0 +1,5 @@
+class AddImportUrlToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :import_url, :string
+ end
+end
diff --git a/db/migrate/20130819182730_add_internal_ids_to_issues_and_mr.rb b/db/migrate/20130819182730_add_internal_ids_to_issues_and_mr.rb
new file mode 100644
index 00000000000..e55ae38f144
--- /dev/null
+++ b/db/migrate/20130819182730_add_internal_ids_to_issues_and_mr.rb
@@ -0,0 +1,6 @@
+class AddInternalIdsToIssuesAndMr < ActiveRecord::Migration
+ def change
+ add_column :issues, :iid, :integer
+ add_column :merge_requests, :iid, :integer
+ end
+end
diff --git a/db/migrate/20130821090530_remove_deprecated_tables.rb b/db/migrate/20130821090530_remove_deprecated_tables.rb
new file mode 100644
index 00000000000..539c0617eeb
--- /dev/null
+++ b/db/migrate/20130821090530_remove_deprecated_tables.rb
@@ -0,0 +1,11 @@
+class RemoveDeprecatedTables < ActiveRecord::Migration
+ def up
+ drop_table :user_teams
+ drop_table :user_team_project_relationships
+ drop_table :user_team_user_relationships
+ end
+
+ def down
+ raise 'No rollback for this migration'
+ end
+end
diff --git a/db/migrate/20130821090531_add_internal_ids_to_milestones.rb b/db/migrate/20130821090531_add_internal_ids_to_milestones.rb
new file mode 100644
index 00000000000..33e5bae5805
--- /dev/null
+++ b/db/migrate/20130821090531_add_internal_ids_to_milestones.rb
@@ -0,0 +1,5 @@
+class AddInternalIdsToMilestones < ActiveRecord::Migration
+ def change
+ add_column :milestones, :iid, :integer
+ end
+end
diff --git a/db/migrate/20130909132950_add_description_to_merge_request.rb b/db/migrate/20130909132950_add_description_to_merge_request.rb
new file mode 100644
index 00000000000..9bcd0c7ee06
--- /dev/null
+++ b/db/migrate/20130909132950_add_description_to_merge_request.rb
@@ -0,0 +1,5 @@
+class AddDescriptionToMergeRequest < ActiveRecord::Migration
+ def change
+ add_column :merge_requests, :description, :text, null: true
+ end
+end
diff --git a/db/migrate/20130926081215_change_owner_id_for_group.rb b/db/migrate/20130926081215_change_owner_id_for_group.rb
new file mode 100644
index 00000000000..8f1992c37ab
--- /dev/null
+++ b/db/migrate/20130926081215_change_owner_id_for_group.rb
@@ -0,0 +1,9 @@
+class ChangeOwnerIdForGroup < ActiveRecord::Migration
+ def up
+ change_column :namespaces, :owner_id, :integer, null: true
+ end
+
+ def down
+ change_column :namespaces, :owner_id, :integer, null: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0f07d2bc8c5..713d9f733d6 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,16 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20130131070232) do
+ActiveRecord::Schema.define(:version => 20130926081215) 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
+ end
+
+ add_index "deploy_keys_projects", ["project_id"], :name => "index_deploy_keys_projects_on_project_id"
create_table "events", :force => true do |t|
t.string "target_type"
@@ -32,23 +41,32 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
add_index "events", ["target_id"], :name => "index_events_on_target_id"
add_index "events", ["target_type"], :name => "index_events_on_target_type"
+ 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
+ end
+
+ add_index "forked_project_links", ["forked_to_project_id"], :name => "index_forked_project_links_on_forked_to_project_id", :unique => true
+
create_table "issues", :force => true do |t|
t.string "title"
t.integer "assignee_id"
t.integer "author_id"
t.integer "project_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.boolean "closed", :default => false, :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.integer "position", :default => 0
t.string "branch_name"
t.text "description"
t.integer "milestone_id"
+ t.string "state"
+ t.integer "iid"
end
add_index "issues", ["assignee_id"], :name => "index_issues_on_assignee_id"
add_index "issues", ["author_id"], :name => "index_issues_on_author_id"
- add_index "issues", ["closed"], :name => "index_issues_on_closed"
add_index "issues", ["created_at"], :name => "index_issues_on_created_at"
add_index "issues", ["milestone_id"], :name => "index_issues_on_milestone_id"
add_index "issues", ["project_id"], :name => "index_issues_on_project_id"
@@ -56,65 +74,66 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
create_table "keys", :force => true do |t|
t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.text "key"
t.string "title"
- t.string "identifier"
- t.integer "project_id"
+ t.string "type"
+ t.string "fingerprint"
end
- add_index "keys", ["identifier"], :name => "index_keys_on_identifier"
- add_index "keys", ["project_id"], :name => "index_keys_on_project_id"
add_index "keys", ["user_id"], :name => "index_keys_on_user_id"
create_table "merge_requests", :force => true do |t|
- t.string "target_branch", :null => false
- t.string "source_branch", :null => false
- t.integer "project_id", :null => false
+ t.string "target_branch", :null => false
+ t.string "source_branch", :null => false
+ t.integer "source_project_id", :null => false
t.integer "author_id"
t.integer "assignee_id"
t.string "title"
- t.boolean "closed", :default => false, :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.text "st_commits", :limit => 2147483647
- t.text "st_diffs", :limit => 2147483647
- t.boolean "merged", :default => false, :null => false
- t.integer "state", :default => 1, :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.text "st_commits", :limit => 2147483647
+ t.text "st_diffs", :limit => 2147483647
t.integer "milestone_id"
+ t.string "state"
+ t.string "merge_status"
+ t.integer "target_project_id", :null => false
+ t.integer "iid"
+ t.text "description"
end
add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id"
add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id"
- add_index "merge_requests", ["closed"], :name => "index_merge_requests_on_closed"
add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at"
add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id"
- add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id"
add_index "merge_requests", ["source_branch"], :name => "index_merge_requests_on_source_branch"
+ add_index "merge_requests", ["source_project_id"], :name => "index_merge_requests_on_project_id"
add_index "merge_requests", ["target_branch"], :name => "index_merge_requests_on_target_branch"
add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title"
create_table "milestones", :force => true do |t|
- t.string "title", :null => false
- t.integer "project_id", :null => false
+ t.string "title", :null => false
+ t.integer "project_id", :null => false
t.text "description"
t.date "due_date"
- t.boolean "closed", :default => false, :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "state"
+ t.integer "iid"
end
add_index "milestones", ["due_date"], :name => "index_milestones_on_due_date"
add_index "milestones", ["project_id"], :name => "index_milestones_on_project_id"
create_table "namespaces", :force => true do |t|
- t.string "name", :null => false
- t.string "path", :null => false
- t.integer "owner_id", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ 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.string "type"
+ t.string "description", :default => "", :null => false
end
add_index "namespaces", ["name"], :name => "index_namespaces_on_name"
@@ -126,17 +145,21 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.text "note"
t.string "noteable_type"
t.integer "author_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.integer "project_id"
t.string "attachment"
t.string "line_code"
t.string "commit_id"
t.integer "noteable_id"
+ t.text "st_diff"
+ t.boolean "system", :default => false, :null => false
end
+ add_index "notes", ["author_id"], :name => "index_notes_on_author_id"
add_index "notes", ["commit_id"], :name => "index_notes_on_commit_id"
add_index "notes", ["created_at"], :name => "index_notes_on_created_at"
+ add_index "notes", ["noteable_id", "noteable_type"], :name => "index_notes_on_noteable_id_and_noteable_type"
add_index "notes", ["noteable_type"], :name => "index_notes_on_noteable_type"
add_index "notes", ["project_id", "noteable_type"], :name => "index_notes_on_project_id_and_noteable_type"
add_index "notes", ["project_id"], :name => "index_notes_on_project_id"
@@ -145,19 +168,26 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.string "name"
t.string "path"
t.text "description"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.integer "creator_id"
t.string "default_branch"
- t.boolean "issues_enabled", :default => true, :null => false
- t.boolean "wall_enabled", :default => true, :null => false
- t.boolean "merge_requests_enabled", :default => true, :null => false
- t.boolean "wiki_enabled", :default => true, :null => false
+ t.boolean "issues_enabled", :default => true, :null => false
+ t.boolean "wall_enabled", :default => true, :null => false
+ t.boolean "merge_requests_enabled", :default => true, :null => false
+ t.boolean "wiki_enabled", :default => true, :null => false
t.integer "namespace_id"
- t.boolean "public", :default => false, :null => false
+ t.boolean "public", :default => false, :null => false
+ t.string "issues_tracker", :default => "gitlab", :null => false
+ t.string "issues_tracker_id"
+ t.boolean "snippets_enabled", :default => true, :null => false
+ t.datetime "last_activity_at"
+ t.boolean "imported", :default => false, :null => false
+ t.string "import_url"
end
add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id"
+ add_index "projects", ["last_activity_at"], :name => "index_projects_on_last_activity_at"
add_index "projects", ["namespace_id"], :name => "index_projects_on_namespace_id"
create_table "protected_branches", :force => true do |t|
@@ -167,6 +197,8 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.datetime "updated_at", :null => false
end
+ add_index "protected_branches", ["project_id"], :name => "index_protected_branches_on_project_id"
+
create_table "services", :force => true do |t|
t.string "type"
t.string "title"
@@ -176,21 +208,26 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.datetime "updated_at", :null => false
t.boolean "active", :default => false, :null => false
t.string "project_url"
+ t.string "subdomain"
+ t.string "room"
end
add_index "services", ["project_id"], :name => "index_services_on_project_id"
create_table "snippets", :force => true do |t|
t.string "title"
- t.text "content"
- t.integer "author_id", :null => false
- t.integer "project_id", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.text "content", :limit => 2147483647
+ t.integer "author_id", :null => false
+ t.integer "project_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.string "file_name"
t.datetime "expires_at"
+ t.boolean "private", :default => true, :null => false
+ t.string "type"
end
+ add_index "snippets", ["author_id"], :name => "index_snippets_on_author_id"
add_index "snippets", ["created_at"], :name => "index_snippets_on_created_at"
add_index "snippets", ["expires_at"], :name => "index_snippets_on_expires_at"
add_index "snippets", ["project_id"], :name => "index_snippets_on_project_id"
@@ -212,31 +249,6 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.string "name"
end
- create_table "user_team_project_relationships", :force => true do |t|
- t.integer "project_id"
- t.integer "user_team_id"
- t.integer "greatest_access"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "user_team_user_relationships", :force => true do |t|
- t.integer "user_id"
- t.integer "user_team_id"
- t.boolean "group_admin"
- t.integer "permission"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "user_teams", :force => true do |t|
- t.string "name"
- t.string "path"
- t.integer "owner_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false
@@ -257,10 +269,8 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.string "linkedin", :default => "", :null => false
t.string "twitter", :default => "", :null => false
t.string "authentication_token"
- t.boolean "dark_scheme", :default => false, :null => false
t.integer "theme_id", :default => 1, :null => false
t.string "bio"
- t.boolean "blocked", :default => false, :null => false
t.integer "failed_attempts", :default => 0
t.datetime "locked_at"
t.string "extern_uid"
@@ -268,22 +278,39 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.string "username"
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.datetime "password_expires_at"
+ t.integer "created_by_id"
end
add_index "users", ["admin"], :name => "index_users_on_admin"
- add_index "users", ["blocked"], :name => "index_users_on_blocked"
+ add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true
add_index "users", ["name"], :name => "index_users_on_name"
add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
add_index "users", ["username"], :name => "index_users_on_username"
+ create_table "users_groups", :force => true do |t|
+ 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.integer "notification_level", :default => 3, :null => false
+ end
+
+ add_index "users_groups", ["user_id"], :name => "index_users_groups_on_user_id"
+
create_table "users_projects", :force => true do |t|
- t.integer "user_id", :null => false
- t.integer "project_id", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "project_access", :default => 0, :null => false
+ t.integer "user_id", :null => false
+ t.integer "project_id", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "project_access", :default => 0, :null => false
+ t.integer "notification_level", :default => 3, :null => false
end
add_index "users_projects", ["project_access"], :name => "index_users_projects_on_project_access"
@@ -299,17 +326,6 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.integer "service_id"
end
- create_table "wikis", :force => true do |t|
- t.string "title"
- t.text "content"
- t.integer "project_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "slug"
- t.integer "user_id"
- end
-
- add_index "wikis", ["project_id"], :name => "index_wikis_on_project_id"
- add_index "wikis", ["slug"], :name => "index_wikis_on_slug"
+ add_index "web_hooks", ["project_id"], :name => "index_web_hooks_on_project_id"
end
diff --git a/doc/api/README.md b/doc/api/README.md
index 0618db7e369..6971e08f010 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -1,6 +1,6 @@
# GitLab API
-All API requests require authentication. You need to pass a `private_token` parameter by url or header. You can find or reset your private token in your profile.
+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:
@@ -18,8 +18,84 @@ Example of a valid API request:
GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U
```
+Example for a valid API request using curl and authentication via header:
+
+```
+curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://example.com/api/v3/projects"
+```
+
+
The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL.
+
+
+## Status codes
+
+The API is designed to return different status codes according to context and action. In this way
+if a request results in an error the caller is able to get insight into what went wrong, e.g.
+status code `400 Bad Request` is returned if a required attribute is missing from the request.
+The following list gives an overview of how the API functions generally behave.
+
+API request types:
+
+* `GET` requests access one or more resources and return the result as JSON
+* `POST` requests return `201 Created` if the resource is successfully created and return the newly created resource as JSON
+* `GET`, `PUT` and `DELETE` return `200 Ok` if the resource is accessed, modified or deleted successfully, the (modified) result is returned as JSON
+* `DELETE` requests are designed to be idempotent, meaning a request a resource still returns `200 Ok` even it was deleted before or is not available. The reasoning behind it is the user is not really interested if the resource existed before or not.
+
+
+The following list shows the possible return codes for API requests.
+
+Return values:
+
+* `200 Ok` - The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON
+* `201 Created` - The `POST` request was successful and the resource is returned as JSON
+* `400 Bad Request` - A required attribute of the API request is missing, e.g. the title of an issue is not given
+* `401 Unauthorized` - The user is not authenticated, a valid user token is necessary, see above
+* `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project
+* `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
+* `405 Method Not Allowed` - The request is not supported
+* `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
+* `500 Server Error` - While handling the request something went wrong on the server side
+
+## Sudo
+All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by url or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals).
+
+If a non administrative `private_token` is provided then an error message will be returned with status code 403:
+
+```json
+{
+ "message": "403 Forbidden: Must be admin to use sudo"
+}
+```
+
+If the sudo user id or username cannot be found then an error message will be returned with status code 404:
+
+```json
+{
+ "message": "404 Not Found: No user id or username for: <id/username>"
+}
+```
+
+Example of a valid API with sudo request:
+
+```
+GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=username
+```
+```
+GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=23
+```
+
+
+Example for a valid API request with sudo using curl and authentication via header:
+
+```
+curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: username" "http://example.com/api/v3/projects"
+```
+```
+curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: 23" "http://example.com/api/v3/projects"
+```
+
#### Pagination
When listing resources you can pass the following parameters:
@@ -29,12 +105,23 @@ When listing resources you can pass the following parameters:
## Contents
-+ [Users](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/users.md)
-+ [Session](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/session.md)
-+ [Projects](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/projects.md)
-+ [Groups](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/groups.md)
-+ [Snippets](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/snippets.md)
-+ [Repositories](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/repositories.md)
-+ [Issues](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/issues.md)
-+ [Milestones](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/milestones.md)
-+ [Notes](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/notes.md)
++ [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)
++ [User Teams](user_teams.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/deploy_keys.md b/doc/api/deploy_keys.md
new file mode 100644
index 00000000000..fbb1e45bccd
--- /dev/null
+++ b/doc/api/deploy_keys.md
@@ -0,0 +1,87 @@
+## Deploy Keys
+
+### List deploy keys
+
+Get a list of a project's deploy keys.
+
+```
+GET /projects/:id/keys
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project
+
+```json
+[
+ {
+ "id": 1,
+ "title" : "Public key",
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
+ 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
+ soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ },
+ {
+ "id": 3,
+ "title" : "Another Public key",
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
+ 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
+ soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ }
+]
+```
+
+
+### Single deploy key
+
+Get a single key.
+
+```
+GET /projects/:id/keys/:key_id
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project
++ `key_id` (required) - The ID of the deploy key
+
+```json
+{
+ "id": 1,
+ "title" : "Public key",
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
+ 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
+ soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+}
+```
+
+
+### Add deploy key
+
+Creates a new deploy key for a project.
+If deploy key already exists in another project - it will be joined to project but only if original one was is accessible by same user
+
+```
+POST /projects/:id/keys
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project
++ `title` (required) - New deploy key's title
++ `key` (required) - New deploy key
+
+
+### Delete deploy key
+
+Delete a deploy key from a project
+
+```
+DELETE /projects/:id/keys/:key_id
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project
++ `key_id` (required) - The ID of the deploy key
+
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 00a7387c76f..9c551fff83a 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -17,7 +17,8 @@ GET /groups
]
```
-## Details of group
+
+## Details of a group
Get all details of a group.
@@ -29,17 +30,90 @@ Parameters:
+ `id` (required) - The ID of a group
+
## New group
-Create a new project group. Available only for admin
+Creates a new project group. Available only for admin.
```
POST /groups
```
Parameters:
-+ `name` (required) - Email
-+ `path` - Password
-Will return created group with status `201 Created` on success, or `404 Not found` on fail.
++ `name` (required) - The name of the group
++ `path` (required) - The path of the group
+
+## Transfer project to group
+
+Transfer a project to the Group namespace. Available only for admin
+
+```
+POST /groups/:id/projects/:project_id
+```
+
+Parameters:
++ `id` (required) - The ID of a group
++ `project_id (required) - The ID of a project
+
+
+## Group members
+
+### List group members
+
+Get a list of group members viewable by the authenticated user.
+
+```
+GET /groups/:id/members
+```
+
+```json
+[
+ {
+ id: 1,
+ username: "raymond_smith",
+ email: "ray@smith.org",
+ name: "Raymond Smith",
+ state: "active",
+ created_at: "2012-10-22T14:13:35Z",
+ access_level: 30
+ },
+ {
+ id: 2,
+ username: "john_doe",
+ email: "joh@doe.org",
+ name: "John Doe",
+ state: "active",
+ created_at: "2012-10-22T14:13:35Z",
+ access_level: 30
+ }
+]
+```
+
+### Add group member
+
+Adds a user to the list of group members.
+
+```
+POST /groups/:id/members
+```
+
+Parameters:
+
++ `id` (required) - The ID of a group
++ `user_id` (required) - The ID of a user to add
++ `access_level` (required) - Project access level
+
+
+### Remove user team member
+
+Removes user from user team.
+
+```
+DELETE /groups/:id/members/:user_id
+```
+
+Parameters:
++ `id` (required) - The ID of a user group
++ `user_id` (required) - The ID of a group member
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 0383b676073..723c8acf381 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -1,6 +1,7 @@
## List issues
-Get all issues created by authenticed user.
+Get all issues created by authenticated user. This function takes pagination parameters
+`page` and `per_page` to restrict the list of issues.
```
GET /issues
@@ -24,7 +25,7 @@ GET /issues
"blocked": false,
"created_at": "2012-05-23T08:00:58Z"
},
- "closed": true,
+ "state": 'closed',
"updated_at": "2012-07-02T17:53:12Z",
"created_at": "2012-07-02T17:53:12Z"
},
@@ -41,7 +42,7 @@ GET /issues
"title": "v1.0",
"description": "",
"due_date": "2012-07-20",
- "closed": false,
+ "state": 'reopenend',
"updated_at": "2012-07-04T13:42:48Z",
"created_at": "2012-07-04T13:42:48Z"
},
@@ -61,16 +62,18 @@ GET /issues
"blocked": false,
"created_at": "2012-05-23T08:00:58Z"
},
- "closed": false,
+ "state": 'opened',
"updated_at": "2012-07-12T13:43:19Z",
"created_at": "2012-06-28T12:58:06Z"
}
]
```
+
## List project issues
-Get a list of project issues.
+Get a list of project issues. This function accepts pagination parameters `page` and `per_page`
+to return the list of project issues.
```
GET /projects/:id/issues
@@ -80,9 +83,10 @@ Parameters:
+ `id` (required) - The ID of a project
+
## Single issue
-Get a project issue.
+Gets a single project issue.
```
GET /projects/:id/issues/:issue_id
@@ -107,7 +111,7 @@ Parameters:
"title": "v1.0",
"description": "",
"due_date": "2012-07-20",
- "closed": false,
+ "state": 'closed',
"updated_at": "2012-07-04T13:42:48Z",
"created_at": "2012-07-04T13:42:48Z"
},
@@ -127,15 +131,16 @@ Parameters:
"blocked": false,
"created_at": "2012-05-23T08:00:58Z"
},
- "closed": false,
+ "state": 'opened',
"updated_at": "2012-07-12T13:43:19Z",
"created_at": "2012-06-28T12:58:06Z"
}
```
+
## New issue
-Create a new project issue.
+Creates a new project issue.
```
POST /projects/:id/issues
@@ -150,11 +155,10 @@ Parameters:
+ `milestone_id` (optional) - The ID of a milestone to assign issue
+ `labels` (optional) - Comma-separated label names for an issue
-Will return created issue with status `201 Created` on success, or `404 Not found` on fail.
## Edit issue
-Update an existing project issue.
+Updates an existing project issue. This function is also used to mark an issue as closed.
```
PUT /projects/:id/issues/:issue_id
@@ -169,7 +173,21 @@ Parameters:
+ `assignee_id` (optional) - The ID of a user to assign issue
+ `milestone_id` (optional) - The ID of a milestone to assign issue
+ `labels` (optional) - Comma-separated label names for an issue
-+ `closed` (optional) - The state of an issue (0 = false, 1 = true)
++ `state_event` (optional) - The state event of an issue ('close' to close issue and 'reopen' to reopen it)
+
+
+## Delete existing issue (**Deprecated**)
+
+The function is deprecated and returns a `405 Method Not Allowed`
+error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with
+parameter `closed` set to 1.
+
+```
+DELETE /projects/:id/issues/:issue_id
+```
+
+Parameters:
-Will return updated issue with status `200 OK` on success, or `404 Not found` on fail.
++ `id` (required) - The project ID
++ `issue_id` (required) - The ID of the issue
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 525c55d12c2..111c52112eb 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -1,6 +1,7 @@
## List merge requests
-Get all MR for this project.
+Get all merge requests for this project. This function takes pagination parameters
+`page` and `per_page` to restrict the list of merge requests.
```
GET /projects/:id/merge_requests
@@ -40,9 +41,10 @@ Parameters:
]
```
-## Show MR
-Show information about MR.
+## Get single MR
+
+Shows information about a single merge request.
```
GET /projects/:id/merge_request/:merge_request_id
@@ -84,7 +86,7 @@ Parameters:
## Create MR
-Create MR.
+Creates a new merge request.
```
POST /projects/:id/merge_requests
@@ -126,9 +128,10 @@ Parameters:
}
```
+
## Update MR
-Update MR. You can change branches, title, or even close the MR.
+Updates an existing merge request. You can change branches, title, or even close the MR.
```
PUT /projects/:id/merge_request/:merge_request_id
@@ -172,9 +175,11 @@ Parameters:
}
}
```
+
+
## Post comment to MR
-Post comment to MR
+Adds a comment to a merge request.
```
POST /projects/:id/merge_request/:merge_request_id/comments
@@ -183,10 +188,9 @@ POST /projects/:id/merge_request/:merge_request_id/comments
Parameters:
+ `id` (required) - The ID of a project
-+ `merge_request_id` (required) - ID of MR
++ `merge_request_id` (required) - ID of merge request
+ `note` (required) - Text of comment
-Will return created note with status `201 Created` on success, or `404 Not found` on fail.
```json
{
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index b997e83901b..aa8f1bf5e02 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -1,6 +1,6 @@
## List project milestones
-Get a list of project milestones.
+Returns a list of project milestones.
```
GET /projects/:id/milestones
@@ -10,9 +10,10 @@ Parameters:
+ `id` (required) - The ID of a project
-## Single milestone
-Get a single project milestone.
+## Get single milestone
+
+Gets a single project milestone.
```
GET /projects/:id/milestones/:milestone_id
@@ -23,9 +24,10 @@ Parameters:
+ `id` (required) - The ID of a project
+ `milestone_id` (required) - The ID of a project milestone
-## New milestone
-Create a new project milestone.
+## Create new milestone
+
+Creates a new project milestone.
```
POST /projects/:id/milestones
@@ -34,14 +36,14 @@ POST /projects/:id/milestones
Parameters:
+ `id` (required) - The ID of a project
-+ `milestone_id` (required) - The ID of a project milestone
+ `title` (required) - The title of an milestone
+ `description` (optional) - The description of the milestone
+ `due_date` (optional) - The due date of the milestone
+
## Edit milestone
-Update an existing project milestone.
+Updates an existing project milestone.
```
PUT /projects/:id/milestones/:milestone_id
@@ -54,4 +56,5 @@ Parameters:
+ `title` (optional) - The title of a milestone
+ `description` (optional) - The description of a milestone
+ `due_date` (optional) - The due date of the milestone
-+ `closed` (optional) - The status of the milestone
++ `state_event` (optional) - The state event of the milestone (close|activate)
+
diff --git a/doc/api/notes.md b/doc/api/notes.md
index a4ba2826076..4b57f636a01 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -1,4 +1,4 @@
-## List notes
+## Wall
### List project wall notes
@@ -30,22 +30,40 @@ Parameters:
+ `id` (required) - The ID of a project
-### List merge request notes
-Get a list of merge request notes.
+### Get single wall note
+
+Returns a single wall note.
```
-GET /projects/:id/merge_requests/:merge_request_id/notes
+GET /projects/:id/notes/:note_id
```
Parameters:
+ `id` (required) - The ID of a project
-+ `merge_request_id` (required) - The ID of an merge request
++ `note_id` (required) - The ID of a wall note
-### List issue notes
-Get a list of issue notes.
+### Create new wall note
+
+Creates a new wall note.
+
+```
+POST /projects/:id/notes
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `body` (required) - The content of a note
+
+
+## Issues
+
+### List project issue notes
+
+Gets a list of all notes for a single issue.
```
GET /projects/:id/issues/:issue_id/notes
@@ -56,54 +74,59 @@ Parameters:
+ `id` (required) - The ID of a project
+ `issue_id` (required) - The ID of an issue
-### List snippet notes
-Get a list of snippet notes.
+### Get single issue note
+
+Returns a single note for a specific project issue
```
-GET /projects/:id/snippets/:snippet_id/notes
+GET /projects/:id/issues/:issue_id/notes/:note_id
```
Parameters:
+ `id` (required) - The ID of a project
-+ `snippet_id` (required) - The ID of a snippet
++ `issue_id` (required) - The ID of a project issue
++ `note_id` (required) - The ID of an issue note
-## Single note
-### Single wall note
+### Create new issue note
-Get a wall note.
+Creates a new note to a single project issue.
```
-GET /projects/:id/notes/:note_id
+POST /projects/:id/issues/:issue_id/notes
```
Parameters:
+ `id` (required) - The ID of a project
-+ `note_id` (required) - The ID of a wall note
++ `issue_id` (required) - The ID of an issue
++ `body` (required) - The content of a note
-### Single issue note
-Get an issue note.
+## Snippets
+
+### List all snippet notes
+
+Gets a list of all notes for a single snippet. Snippet notes are comments users can post to a snippet.
```
-GET /projects/:id/issues/:issue_id/:notes/:note_id
+GET /projects/:id/snippets/:snippet_id/notes
```
Parameters:
+ `id` (required) - The ID of a project
-+ `issue_id` (required) - The ID of a project issue
-+ `note_id` (required) - The ID of an issue note
++ `snippet_id` (required) - The ID of a project snippet
+
-### Single snippet note
+### Get single snippet note
-Get a snippet note.
+Returns a single note for a given snippet.
```
-GET /projects/:id/issues/:snippet_id/:notes/:note_id
+GET /projects/:id/snippets/:snippet_id/notes/:note_id
```
Parameters:
@@ -112,52 +135,64 @@ Parameters:
+ `snippet_id` (required) - The ID of a project snippet
+ `note_id` (required) - The ID of an snippet note
-## New note
-### New wall note
+### Create new snippet note
-Create a new wall note.
+Creates a new note for a single snippet. Snippet notes are comments users can post to a snippet.
```
-POST /projects/:id/notes
+POST /projects/:id/snippets/:snippet_id/notes
```
Parameters:
+ `id` (required) - The ID of a project
++ `snippet_id` (required) - The ID of an snippet
+ `body` (required) - The content of a note
-Will return created note with status `201 Created` on success, or `404 Not found` on fail.
+## Merge Requests
-### New issue note
+### List all merge request notes
-Create a new issue note.
+Gets a list of all notes for a single merge request.
```
-POST /projects/:id/issues/:issue_id/notes
+GET /projects/:id/merge_requests/:merge_request_id/notes
```
Parameters:
+ `id` (required) - The ID of a project
-+ `issue_id` (required) - The ID of an issue
-+ `body` (required) - The content of a note
++ `merge_request_id` (required) - The ID of a project merge request
-Will return created note with status `201 Created` on success, or `404 Not found` on fail.
-### New snippet note
+### Get single merge request note
-Create a new snippet note.
+Returns a single note for a given merge request.
```
-POST /projects/:id/snippets/:snippet_id/notes
+GET /projects/:id/merge_requests/:merge_request_id/notes/:note_id
```
Parameters:
+ `id` (required) - The ID of a project
-+ `snippet_id` (required) - The ID of an snippet
++ `merge_request_id` (required) - The ID of a project merge request
++ `note_id` (required) - The ID of a merge request note
+
+
+### Create new merge request note
+
+Creates a new note for a single merge request.
+
+```
+POST /projects/:id/merge_requests/:merge_request_id/notes
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `merge_request_id` (required) - The ID of a merge request
+ `body` (required) - The content of a note
-Will return created note with status `201 Created` on success, or `404 Not found` on fail.
diff --git a/doc/api/snippets.md b/doc/api/project_snippets.md
index ceb8a63d06f..04ea367d518 100644
--- a/doc/api/snippets.md
+++ b/doc/api/project_snippets.md
@@ -10,9 +10,10 @@ Parameters:
+ `id` (required) - The ID of a project
+
## Single snippet
-Get a project snippet.
+Get a single project snippet.
```
GET /projects/:id/snippets/:snippet_id
@@ -42,22 +43,10 @@ Parameters:
}
```
-## Snippet content
-
-Get a raw project snippet.
-
-```
-GET /projects/:id/snippets/:snippet_id/raw
-```
-
-Parameters:
-
-+ `id` (required) - The ID of a project
-+ `snippet_id` (required) - The ID of a project's snippet
-## New snippet
+## Create new snippet
-Create a new project snippet.
+Creates a new project snippet. The user must have permission to create new snippets.
```
POST /projects/:id/snippets
@@ -71,11 +60,10 @@ Parameters:
+ `lifetime` (optional) - The expiration date of a snippet
+ `code` (required) - The content of a snippet
-Will return created snippet with status `201 Created` on success, or `404 Not found` on fail.
-## Edit snippet
+## Update snippet
-Update an existing project snippet.
+Updates an existing project snippet. The user must have permission to change an existing snippet.
```
PUT /projects/:id/snippets/:snippet_id
@@ -90,11 +78,11 @@ Parameters:
+ `lifetime` (optional) - The expiration date of a snippet
+ `code` (optional) - The content of a snippet
-Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail.
## Delete snippet
-Delete existing project snippet.
+Deletes an existing project snippet. This is an idempotent function and deleting a non-existent
+snippet still returns a `200 Ok` status code.
```
DELETE /projects/:id/snippets/:snippet_id
@@ -105,5 +93,16 @@ Parameters:
+ `id` (required) - The ID of a project
+ `snippet_id` (required) - The ID of a project's snippet
-Status code `200` will be returned on success.
+## Snippet content
+
+Returns the raw project snippet as plain text.
+
+```
+GET /projects/:id/snippets/:snippet_id/raw
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `snippet_id` (required) - The ID of a project's snippet
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 82bb0c0d561..5150331e7d7 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -1,4 +1,6 @@
-## List projects
+## Projects
+
+### List projects
Get a list of projects owned by the authenticated user.
@@ -21,14 +23,15 @@ GET /projects
"blocked": false,
"created_at": "2012-05-23T08:00:58Z"
},
- "private": true,
+ "public": true,
"path": "rails",
"path_with_namespace": "rails/rails",
"issues_enabled": false,
"merge_requests_enabled": false,
"wall_enabled": true,
"wiki_enabled": true,
- "created_at": "2012-05-23T08:05:02Z"
+ "created_at": "2012-05-23T08:05:02Z",
+ "last_activity_at": "2012-05-23T08:05:02Z"
},
{
"id": 5,
@@ -43,21 +46,25 @@ GET /projects
"blocked": false,
"created_at": "2012-05-23T08:00:58Z"
},
- "private": true,
+ "public": true,
"path": "gitlab",
"path_with_namespace": "randx/gitlab",
"issues_enabled": true,
"merge_requests_enabled": true,
"wall_enabled": true,
"wiki_enabled": true,
- "created_at": "2012-05-30T12:49:20Z"
+ "snippets_enabled": true,
+ "created_at": "2012-05-30T12:49:20Z",
+ "last_activity_at": "2012-05-23T08:05:02Z"
}
]
```
-## Single project
-Get a specific project, identified by project ID, which is owned by the authentication user.
+### Get single project
+
+Get a specific project, identified by project ID or NAME, which is owned by the authentication user.
+Currently namespaced projects cannot retrieved by name.
```
GET /projects/:id
@@ -65,12 +72,13 @@ GET /projects/:id
Parameters:
-+ `id` (required) - The ID of a project
++ `id` (required) - The ID or NAME of a project
```json
{
"id": 5,
"name": "gitlab",
+ "name_with_namespace": "GitLab / gitlabhq",
"description": null,
"default_branch": "api",
"owner": {
@@ -81,20 +89,91 @@ Parameters:
"blocked": false,
"created_at": "2012-05-23T08:00:58Z"
},
- "private": true,
+ "public": true,
"path": "gitlab",
"path_with_namespace": "randx/gitlab",
"issues_enabled": true,
"merge_requests_enabled": true,
"wall_enabled": true,
"wiki_enabled": true,
- "created_at": "2012-05-30T12:49:20Z"
+ "snippets_enabled": true,
+ "created_at": "2012-05-30T12:49:20Z",
+ "last_activity_at": "2012-05-23T08:05:02Z"
}
```
-## Create project
+### Get project events
+
+Get a project events for specific project.
+Sorted from newest to latest
+
+```
+GET /projects/:id/events
+```
+
+Parameters:
+
++ `id` (required) - The ID or NAME of a project
+
+```json
+
+[{
+ "title": null,
+ "project_id": 15,
+ "action_name": "closed",
+ "target_id": 830,
+ "target_type": "Issue",
+ "author_id": 1,
+ "data": null,
+ "target_title": "Public project search field"
+}, {
+ "title": null,
+ "project_id": 15,
+ "action_name": "opened",
+ "target_id": null,
+ "target_type": null,
+ "author_id": 1,
+ "data": {
+ "before": "50d4420237a9de7be1304607147aec22e4a14af7",
+ "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
+ "ref": "refs/heads/master",
+ "user_id": 1,
+ "user_name": "Dmitriy Zaporozhets",
+ "repository": {
+ "name": "gitlabhq",
+ "url": "git@dev.gitlab.org:gitlab/gitlabhq.git",
+ "description": "GitLab: self hosted Git management software. \r\nDistributed under the MIT License.",
+ "homepage": "https://dev.gitlab.org/gitlab/gitlabhq"
+ },
+ "commits": [{
+ "id": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
+ "message": "Add simple search to projects in public area",
+ "timestamp": "2013-05-13T18:18:08+00:00",
+ "url": "https://dev.gitlab.org/gitlab/gitlabhq/commit/c5feabde2d8cd023215af4d2ceeb7a64839fc428",
+ "author": {
+ "name": "Dmitriy Zaporozhets",
+ "email": "dmitriy.zaporozhets@gmail.com"
+ }
+ }],
+ "total_commits_count": 1
+ },
+ "target_title": null
+}, {
+ "title": null,
+ "project_id": 15,
+ "action_name": "closed",
+ "target_id": 840,
+ "target_type": "Issue",
+ "author_id": 1,
+ "data": null,
+ "target_title": "Finish & merge Code search PR"
+}]
+```
+
-Create new project owned by user
+### Create project
+
+Creates new project owned by user.
```
POST /projects
@@ -105,15 +184,51 @@ Parameters:
+ `name` (required) - new project name
+ `description` (optional) - short project description
+ `default_branch` (optional) - 'master' by default
-+ `issues_enabled` (optional) - enabled by default
-+ `wall_enabled` (optional) - enabled by default
-+ `merge_requests_enabled` (optional) - enabled by default
-+ `wiki_enabled` (optional) - enabled by default
++ `issues_enabled` (optional)
++ `wall_enabled` (optional)
++ `merge_requests_enabled` (optional)
++ `wiki_enabled` (optional)
++ `snippets_enabled` (optional)
++ `public` (optional)
+
+**Project access levels**
+
+The project access levels are defined in the `user_project.rb` class. Currently, these levels are recognized:
+
+```
+ GUEST = 10
+ REPORTER = 20
+ DEVELOPER = 30
+ MASTER = 40
+```
+
-Will return created project with status `201 Created` on success, or `404 Not
-found` on fail.
+### Create project for user
-## List project team members
+Creates a new project owned by user. Available only for admins.
+
+```
+POST /projects/user/:user_id
+```
+
+Parameters:
+
++ `user_id` (required) - user_id of owner
++ `name` (required) - new project name
++ `description` (optional) - short project description
++ `default_branch` (optional) - 'master' by default
++ `issues_enabled` (optional)
++ `wall_enabled` (optional)
++ `merge_requests_enabled` (optional)
++ `wiki_enabled` (optional)
++ `snippets_enabled` (optional)
++ `public` (optional)
+
+
+
+## Team members
+
+### List project team members
Get a list of project team members.
@@ -123,12 +238,13 @@ GET /projects/:id/members
Parameters:
-+ `id` (required) - The ID of a project
-+ `query` - Query string
++ `id` (required) - The ID or NAME of a project
++ `query` (optional) - Query string to search for members
+
-## Get project team member
+### Get project team member
-Get a project team member.
+Gets a project team member.
```
GET /projects/:id/members/:user_id
@@ -136,12 +252,11 @@ GET /projects/:id/members/:user_id
Parameters:
-+ `id` (required) - The ID of a project
++ `id` (required) - The ID or NAME of a project
+ `user_id` (required) - The ID of a user
```json
{
-
"id": 1,
"username": "john_smith",
"email": "john@example.com",
@@ -152,9 +267,12 @@ Parameters:
}
```
-## Add project team member
-Add a user to a project team.
+### Add project team member
+
+Adds a user to a project team. This is an idempotent method and can be called multiple times
+with the same parameters. Adding team membership to a user that is already a member does not
+affect the existing membership.
```
POST /projects/:id/members
@@ -162,15 +280,14 @@ POST /projects/:id/members
Parameters:
-+ `id` (required) - The ID of a project
++ `id` (required) - The ID or NAME of a project
+ `user_id` (required) - The ID of a user to add
+ `access_level` (required) - Project access level
-Will return status `201 Created` on success, or `404 Not found` on fail.
-## Edit project team member
+### Edit project team member
-Update project team member to specified access level.
+Updates project team member to a specified access level.
```
PUT /projects/:id/members/:user_id
@@ -178,13 +295,12 @@ PUT /projects/:id/members/:user_id
Parameters:
-+ `id` (required) - The ID of a project
++ `id` (required) - The ID or NAME of a project
+ `user_id` (required) - The ID of a team member
+ `access_level` (required) - Project access level
-Will return status `200 OK` on success, or `404 Not found` on fail.
-## Remove project team member
+### Remove project team member
Removes user from project team.
@@ -194,14 +310,20 @@ DELETE /projects/:id/members/:user_id
Parameters:
-+ `id` (required) - The ID of a project
++ `id` (required) - The ID or NAME of a project
+ `user_id` (required) - The ID of a team member
-Status code `200` will be returned on success.
+This method is idempotent and can be called multiple times with the same parameters.
+Revoking team membership for a user who is not currently a team member is considered success.
+Please note that the returned JSON currently differs slightly. Thus you should not
+rely on the returned JSON structure.
-## List project hooks
-Get list for project hooks
+## Hooks
+
+### List project hooks
+
+Get list of project hooks.
```
GET /projects/:id/hooks
@@ -209,13 +331,12 @@ GET /projects/:id/hooks
Parameters:
-+ `id` (required) - The ID of a project
++ `id` (required) - The ID or NAME of a project
-Will return hooks with status `200 OK` on success, or `404 Not found` on fail.
-## Get project hook
+### Get project hook
-Get hook for project
+Get a specific hook for project.
```
GET /projects/:id/hooks/:hook_id
@@ -223,14 +344,21 @@ GET /projects/:id/hooks/:hook_id
Parameters:
-+ `id` (required) - The ID of a project
++ `id` (required) - The ID or NAME of a project
+ `hook_id` (required) - The ID of a project hook
-Will return hook with status `200 OK` on success, or `404 Not found` on fail.
+```json
+{
+ "id": 1,
+ "url": "http://example.com/hook",
+ "created_at": "2012-10-12T17:04:47Z"
+}
+```
-## Add project hook
-Add hook to project
+### Add project hook
+
+Adds a hook to project.
```
POST /projects/:id/hooks
@@ -238,14 +366,13 @@ POST /projects/:id/hooks
Parameters:
-+ `id` (required) - The ID of a project
++ `id` (required) - The ID or NAME of a project
+ `url` (required) - The hook URL
-Will return status `201 Created` on success, or `404 Not found` on fail.
-## Edit project hook
+### Edit project hook
-Edit hook for project
+Edits a hook for project.
```
PUT /projects/:id/hooks/:hook_id
@@ -253,24 +380,122 @@ PUT /projects/:id/hooks/:hook_id
Parameters:
-+ `id` (required) - The ID of a project
++ `id` (required) - The ID or NAME of a project
+ `hook_id` (required) - The ID of a project hook
+ `url` (required) - The hook URL
-Will return status `201 Created` on success, or `404 Not found` on fail.
-
-## Delete project hook
+### Delete project hook
-Delete hook from project
+Removes a hook from project. This is an idempotent method and can be called multiple times.
+Either the hook is available or not.
```
-DELETE /projects/:id/hooks
+DELETE /projects/:id/hooks/:hook_id
```
Parameters:
-+ `id` (required) - The ID of a project
++ `id` (required) - The ID or NAME of a project
+ `hook_id` (required) - The ID of hook to delete
-Will return status `200 OK` on success, or `404 Not found` on fail.
+Note the JSON response differs if the hook is available or not. If the project hook
+is available before it is returned in the JSON response or an empty response is returned.
+
+
+## Branches
+
+### List branches
+
+Lists all branches of a project.
+
+```
+GET /projects/:id/repository/branches
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project
+
+
+### List single branch
+
+Lists a specific branch of a project.
+
+```
+GET /projects/:id/repository/branches/:branch
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project.
++ `branch` (required) - The name of the branch.
+
+
+### Protect single branch
+
+Protects a single branch of a project.
+
+```
+PUT /projects/:id/repository/branches/:branch/protect
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project.
++ `branch` (required) - The name of the branch.
+
+
+### Unprotect single branch
+
+Unprotects a single branch of a project.
+
+```
+PUT /projects/:id/repository/branches/:branch/unprotect
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project.
++ `branch` (required) - The name of the branch.
+
+
+## Admin fork relation
+
+Allows modification of the forked relationship between existing projects. . Available only for admins.
+
+### Create a forked from/to relation between existing projects.
+
+```
+POST /projects/:id/fork/:forked_from_id
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project
++ `forked_from_id:` (required) - The ID of the project that was forked from
+
+### Delete an existing forked from relationship
+
+```
+DELETE /projects/:id/fork
+```
+
+Parameter:
+
++ `id` (required) - The ID of the project
+
+
+## Search for projects by name
+
+Search for projects by name which are public or the calling user has access to
+
+```
+GET /projects/search/:query
+```
+
+Parameters:
+
++ query (required) - A string contained in the project name
++ per_page (optional) - number of projects to return per page
++ page (optional) - the page to retrieve
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index fd0ef1f53eb..cb0626972e5 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -1,4 +1,4 @@
-## Project repository branches
+## List repository branches
Get a list of repository branches from a project, sorted by name alphabetically.
@@ -39,7 +39,8 @@ Parameters:
]
```
-## Project repository branch
+
+## Get single repository branch
Get a single project repository branch.
@@ -79,12 +80,11 @@ Parameters:
}
```
-Will return status code `200` on success or `404 Not found` if the branch is not available.
-
-## Protect a project repository branch
+## Protect repository branch
-Protect a single project repository branch.
+Protects a single project repository branch. This is an idempotent function, protecting an already
+protected repository branch still returns a `200 Ok` status code.
```
PUT /projects/:id/repository/branches/:branch/protect
@@ -122,9 +122,11 @@ Parameters:
}
```
-## Unprotect a project repository branch
-Unprotect a single project repository branch.
+## Unprotect repository branch
+
+Unprotects a single project repository branch. This is an idempotent function, unprotecting an already
+unprotected repository branch still returns a `200 Ok` status code.
```
PUT /projects/:id/repository/branches/:branch/unprotect
@@ -162,7 +164,8 @@ Parameters:
}
```
-## Project repository tags
+
+## List project repository tags
Get a list of repository tags from a project, sorted by name in reverse alphabetical order.
@@ -201,7 +204,8 @@ Parameters:
]
```
-## Project repository commits
+
+## List repository commits
Get a list of repository commits in a project.
@@ -212,7 +216,7 @@ GET /projects/:id/repository/commits
Parameters:
+ `id` (required) - The ID of a project
-+ `ref_name` (optional) - The name of a repository branch or tag
++ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
```json
[
@@ -235,12 +239,116 @@ Parameters:
]
```
+## 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"
+}
+```
+
+
+## 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.
+
+```
+GET /projects/:id/repository/tree
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `path` (optional) - The path inside repository. Used to get contend of subdirectories
++ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
+
+```json
+
+[{
+ "name": "assets",
+ "type": "tree",
+ "mode": "040000",
+ "id": "6229c43a7e16fcc7e95f923f8ddadb8281d9c6c6"
+}, {
+ "name": "contexts",
+ "type": "tree",
+ "mode": "040000",
+ "id": "faf1cdf33feadc7973118ca42d35f1e62977e91f"
+}, {
+ "name": "controllers",
+ "type": "tree",
+ "mode": "040000",
+ "id": "95633e8d258bf3dfba3a5268fb8440d263218d74"
+}, {
+ "name": "Rakefile",
+ "type": "blob",
+ "mode": "100644",
+ "id": "35b2f05cbb4566b71b34554cf184a9d0bd9d46d6"
+}, {
+ "name": "VERSION",
+ "type": "blob",
+ "mode": "100644",
+ "id": "803e4a4f3727286c3093c63870c2b6524d30ec4f"
+}, {
+ "name": "config.ru",
+ "type": "blob",
+ "mode": "100644",
+ "id": "dfd2d862237323aa599be31b473d70a8a817943b"
+}]
+
+```
+
+
## Raw blob content
Get the raw file contents for a file.
```
-GET /projects/:id/repository/commits/:sha/blob
+GET /projects/:id/repository/blobs/:sha
```
Parameters:
@@ -248,5 +356,3 @@ Parameters:
+ `id` (required) - The ID of a project
+ `sha` (required) - The commit or branch name
+ `filepath` (required) - The path the file
-
-Will return the raw file contents.
diff --git a/doc/api/session.md b/doc/api/session.md
index c7e57aaca7a..162d4c8bf78 100644
--- a/doc/api/session.md
+++ b/doc/api/session.md
@@ -6,10 +6,13 @@ POST /session
Parameters:
-+ `email` (required) - The email of user
++ `login` (required) - The login of user
++ `email` (required if login missing) - The email of user
+ `password` (required) - Valid password
+__You can login with both GitLab and LDAP credentials now__
+
```json
{
"id": 1,
@@ -17,7 +20,17 @@ Parameters:
"email": "john@example.com",
"name": "John Smith",
"private_token": "dd34asd13as",
+ "blocked": false,
"created_at": "2012-05-23T08:00:58Z",
- "blocked": true
+ "bio": null,
+ "skype": "",
+ "linkedin": "",
+ "twitter": "",
+ "dark_scheme": false,
+ "theme_id": 1,
+ "is_admin": false,
+ "can_create_group" : true,
+ "can_create_team" : true,
+ "can_create_project" : true
}
```
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
new file mode 100644
index 00000000000..dca22c43f83
--- /dev/null
+++ b/doc/api/system_hooks.md
@@ -0,0 +1,49 @@
+All methods require admin authorization.
+
+## List system hooks
+
+Get list of system hooks
+
+```
+GET /hooks
+```
+
+Parameters:
+
++ **none**
+
+
+## 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
+
+
+## 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/api/user_teams.md b/doc/api/user_teams.md
new file mode 100644
index 00000000000..cf467a54667
--- /dev/null
+++ b/doc/api/user_teams.md
@@ -0,0 +1,209 @@
+## User teams
+
+### List user teams
+
+Get a list of user teams viewable by the authenticated user.
+
+```
+GET /user_teams
+```
+
+```json
+[
+ {
+ id: 1,
+ name: "User team 1",
+ path: "user_team1",
+ owner_id: 1
+ },
+ {
+ id: 2,
+ name: "User team 2",
+ path: "user_team2",
+ owner_id: 1
+ }
+]
+```
+
+
+### Get single user team
+
+Get a specific user team, identified by user team ID, which is viewable by the authenticated user.
+
+```
+GET /user_teams/:id
+```
+
+Parameters:
+
++ `id` (required) - The ID of a user_team
+
+```json
+{
+ id: 1,
+ name: "User team 1",
+ path: "user_team1",
+ owner_id: 1
+}
+```
+
+
+### Create user team
+
+Creates new user team owned by user. Available only for admins.
+
+```
+POST /user_teams
+```
+
+Parameters:
+
++ `name` (required) - new user team name
++ `path` (required) - new user team internal name
+
+
+
+## User team members
+
+### List user team members
+
+Get a list of project team members.
+
+```
+GET /user_teams/:id/members
+```
+
+Parameters:
+
++ `id` (required) - The ID of a user_team
+
+
+### Get user team member
+
+Gets a user team member.
+
+```
+GET /user_teams/:id/members/:user_id
+```
+
+Parameters:
+
++ `id` (required) - The ID of a user_team
++ `user_id` (required) - The ID of a user
+
+```json
+{
+ id: 2,
+ username: "john_doe",
+ email: "joh@doe.org",
+ name: "John Doe",
+ state: "active",
+ created_at: "2012-10-22T14:13:35Z",
+ access_level: 30
+}
+```
+
+
+### Add user team member
+
+Adds a user to a user team.
+
+```
+POST /user_teams/:id/members
+```
+
+Parameters:
+
++ `id` (required) - The ID of a user team
++ `user_id` (required) - The ID of a user to add
++ `access_level` (required) - Project access level
+
+
+### Remove user team member
+
+Removes user from user team.
+
+```
+DELETE /user_teams/:id/members/:user_id
+```
+
+Parameters:
+
++ `id` (required) - The ID of a user team
++ `user_id` (required) - The ID of a team member
+
+## User team projects
+
+### List user team projects
+
+Get a list of project team projects.
+
+```
+GET /user_teams/:id/projects
+```
+
+Parameters:
+
++ `id` (required) - The ID of a user_team
+
+
+### Get user team project
+
+Gets a user team project.
+
+```
+GET /user_teams/:id/projects/:project_id
+```
+
+Parameters:
+
++ `id` (required) - The ID of a user_team
++ `project_id` (required) - The ID of a user
+
+```json
+{
+ id: 12,
+ name: "project1",
+ description: null,
+ default_branch: "develop",
+ public: false,
+ path: "project1",
+ path_with_namespace: "group1/project1",
+ issues_enabled: false,
+ merge_requests_enabled: true,
+ wall_enabled: true,
+ wiki_enabled: false,
+ created_at: "2013-03-11T12:59:08Z",
+ greatest_access_level: 30
+}
+```
+
+
+### Add user team project
+
+Adds a project to a user team.
+
+```
+POST /user_teams/:id/projects
+```
+
+Parameters:
+
++ `id` (required) - The ID of a user team
++ `project_id` (required) - The ID of a project to add
++ `greatest_access_level` (required) - Maximum project access level
+
+
+### Remove user team project
+
+Removes project from user team.
+
+```
+DELETE /user_teams/:id/projects/:project_id
+```
+
+Parameters:
+
++ `id` (required) - The ID of a user team
++ `project_id` (required) - The ID of a team project
+
diff --git a/doc/api/users.md b/doc/api/users.md
index b94d7c0f789..49afbab8c6a 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -1,6 +1,7 @@
## List users
Get a list of users.
+This function takes pagination parameters `page` and `per_page` to restrict the list of users.
```
GET /users
@@ -13,36 +14,37 @@ GET /users
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
- "blocked": false,
+ "state": "active",
"created_at": "2012-05-23T08:00:58Z",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
- "dark_scheme": false,
"extern_uid": "john.smith",
"provider": "provider_name",
- "theme_id": 1
+ "theme_id": 1,
+ "color_scheme_id": 2
},
{
"id": 2,
"username": "jack_smith",
"email": "jack@example.com",
"name": "Jack Smith",
- "blocked": false,
+ "state": "blocked",
"created_at": "2012-05-23T08:01:01Z",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
- "dark_scheme": true,
"extern_uid": "jack.smith",
"provider": "provider_name",
- "theme_id": 1
+ "theme_id": 1,
+ "color_scheme_id": 3
}
]
```
+
## Single user
Get a single user.
@@ -61,50 +63,53 @@ Parameters:
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
- "blocked": false,
+ "state": "active",
"created_at": "2012-05-23T08:00:58Z",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
- "dark_scheme": false,
"extern_uid": "john.smith",
"provider": "provider_name",
- "theme_id": 1
+ "theme_id": 1,
+ "color_scheme_id": 2
}
```
+
## User creation
-Create user. Available only for admin
+
+Creates a new user. Note only administrators can create new users.
```
POST /users
```
Parameters:
-+ `email` (required) - Email
-+ `password` (required) - Password
-+ `username` (required) - Username
-+ `name` (required) - Name
-+ `skype` - Skype ID
-+ `linkedin` - Linkedin
-+ `twitter` - Twitter account
-+ `projects_limit` - Number of projects user can create
-+ `extern_uid` - External UID
-+ `provider` - External provider name
-+ `bio` - User's bio
-Will return created user with status `201 Created` on success, or `404 Not
-found` on fail.
++ `email` (required) - Email
++ `password` (required) - Password
++ `username` (required) - Username
++ `name` (required) - Name
++ `skype` (optional) - Skype ID
++ `linkedin` (optional) - Linkedin
++ `twitter` (optional) - Twitter account
++ `projects_limit` (optional) - Number of projects user can create
++ `extern_uid` (optional) - External UID
++ `provider` (optional) - External provider name
++ `bio` (optional) - User's bio
+
## User modification
-Modify user. Available only for admin
+
+Modifies an existing user. Only administrators can change attributes of a user.
```
PUT /users/:id
```
Parameters:
+
+ `email` - Email
+ `username` - Username
+ `name` - Name
@@ -112,28 +117,33 @@ Parameters:
+ `skype` - Skype ID
+ `linkedin` - Linkedin
+ `twitter` - Twitter account
-+ `projects_limit` - Limit projects wich user can create
++ `projects_limit` - Limit projects each user can create
+ `extern_uid` - External UID
+ `provider` - External provider name
+ `bio` - User's bio
+Note, at the moment this method does only return a 404 error, even in cases where a 409 (Conflict) would
+be more appropriate, e.g. when renaming the email address to some existing one.
-Will return created user with status `200 OK` on success, or `404 Not
-found` on fail.
## User deletion
-Delete user. Available only for admin
+
+Deletes a user. Available only for administrators. This is an idempotent function, calling this function
+for a non-existent user id still returns a status code `200 Ok`. The JSON response differs if the user
+was actually deleted or not. In the former the user is returned and in the latter not.
```
DELETE /users/:id
```
-Will return deleted user with status `200 OK` on success, or `404 Not
-found` on fail.
+Parameters:
+
++ `id` (required) - The ID of the user
+
## Current user
-Get currently authenticated user.
+Gets currently authenticated user.
```
GET /user
@@ -145,17 +155,23 @@ GET /user
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
- "blocked": false,
+ "private_token": "dd34asd13as",
+ "state": "active",
"created_at": "2012-05-23T08:00:58Z",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
- "dark_scheme": false,
- "theme_id": 1
+ "theme_id": 1,
+ "color_scheme_id": 2,
+ "is_admin": false,
+ "can_create_group" : true,
+ "can_create_team" : true,
+ "can_create_project" : true
}
```
+
## List SSH keys
Get a list of currently authenticated user's SSH keys.
@@ -168,14 +184,14 @@ GET /user/keys
[
{
"id": 1,
- "title" : "Public key"
+ "title" : "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
},
{
"id": 3,
- "title" : "Another Public key"
+ "title" : "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
@@ -183,6 +199,11 @@ GET /user/keys
]
```
+Parameters:
+
++ **none**
+
+
## Single SSH key
Get a single key.
@@ -198,15 +219,17 @@ Parameters:
```json
{
"id": 1,
- "title" : "Public key"
+ "title" : "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
}
```
+
+
## Add SSH key
-Create new key owned by currently authenticated user
+Creates a new key owned by the currently authenticated user.
```
POST /user/keys
@@ -217,12 +240,28 @@ Parameters:
+ `title` (required) - new SSH Key's title
+ `key` (required) - new SSH key
+
+## Add SSH key for user
+
+Create new key owned by specified user. Available only for admin
+
+```
+POST /users/:id/keys
+```
+
+Parameters:
+
++ `id` (required) - id of specified user
++ `title` (required) - new SSH Key's title
++ `key` (required) - new SSH key
+
Will return created key with status `201 Created` on success, or `404 Not
found` on fail.
## Delete SSH key
-Delete key owned by currently authenticated user
+Deletes key owned by currently authenticated user. This is an idempotent function and calling it on a key that is already
+deleted or not available results in `200 Ok`.
```
DELETE /user/keys/:id
@@ -232,4 +271,3 @@ Parameters:
+ `id` (required) - SSH key ID
-Will return `200 OK` on success, or `404 Not Found` on fail.
diff --git a/doc/install/databases.md b/doc/install/databases.md
index 4c6c084d0b9..6477e1c967c 100644
--- a/doc/install/databases.md
+++ b/doc/install/databases.md
@@ -11,23 +11,43 @@ GitLab supports the following databases:
# Install the database packages
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
+ # Pick a database root password (can be anything), type it and press enter
+ # Retype the database root password and press enter
+
+ # Secure your installation.
+ sudo mysql_secure_installation
+
# Login to MySQL
- $ mysql -u root -p
+ mysql -u root -p
- # Create a user for GitLab. (change $password to a real password)
+ # Type the database root password
+
+ # Create a user for GitLab
+ # do not type the 'mysql>', this is part of the prompt
+ # change $password in the command below to a real password you pick
mysql> CREATE USER 'gitlab'@'localhost' IDENTIFIED BY '$password';
# Create the GitLab production database
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
- # Grant the GitLab user necessary permissopns on the table.
- mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'gitlab'@'localhost';
+ # Grant the GitLab user necessary permissions on the table.
+ mysql> GRANT SELECT, LOCK TABLES, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'gitlab'@'localhost';
# Quit the database session
mysql> \q
# Try connecting to the new database with the new user
- sudo -u gitlab -H mysql -u gitlab -p -D gitlabhq_production
+ sudo -u git -H mysql -u gitlab -p -D gitlabhq_production
+
+ # Type the password you replaced $password with earlier
+
+ # You should now see a 'mysql>' prompt
+
+ # Quit the database session
+ mysql> \q
+
+ # You are done installing the database and can go back to the rest of the installation.
+
## PostgreSQL
@@ -38,14 +58,14 @@ GitLab supports the following databases:
sudo -u postgres psql -d template1
# Create a user for GitLab. (change $password to a real password)
- template1=# CREATE USER gitlab WITH PASSWORD '$password';
+ template1=# CREATE USER git WITH PASSWORD '$password';
# Create the GitLab production database & grant all privileges on database
- template1=# CREATE DATABASE gitlabhq_production OWNER gitlab;
+ 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 gitlab -H psql -d gitlabhq_production
+ sudo -u git -H psql -d gitlabhq_production
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 501ae6db87a..71a587d2ee3 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -1,19 +1,20 @@
-This installation guide was created for Debian/Ubuntu and tested on it.
+# Select Version to Install
+Make sure you view this installation guide from the branch (version) of GitLab you would like to install. In most cases
+this should be the highest numbered stable branch (example shown below).
-Please read `doc/install/requirements.md` for hardware and platform requirements.
+![capture](https://f.cloud.github.com/assets/1192780/564911/2f9f3e1e-c5b7-11e2-9f89-98e527d1adec.png)
+If this is unclear check the [GitLab Blog](http://blog.gitlab.org/) for installation guide links by version.
-**Important Note:**
-The following steps have been known to work.
-If you deviate from this guide, do it with caution and make sure you don't
-violate any assumptions GitLab makes about its environment.
-For things like AWS installation scripts, init scripts or config files for
-alternative web server have a look at the "Advanced Setup Tips" section.
+# Important notes
+This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [`doc/install/requirements.md`](./requirements.md) for hardware and operating system requirements.
-**Important Note:**
-If you find a bug/error in this guide please submit an issue or pull request
-following the contribution guide (see `CONTRIBUTING.md`).
+This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please consult [the installation section in the readme](https://github.com/gitlabhq/gitlabhq#installation).
+
+The following steps have been known to work. Please **use caution when you deviate** from this guide. Make sure you don't violate any assumptions GitLab makes about its environment. For example many people run into permission problems because they changed the location of directories or run services as the wrong user.
+
+If you find a bug/error in this guide please **submit a pull request** following the [contributing guide](../../CONTRIBUTING.md).
- - -
@@ -24,7 +25,7 @@ The GitLab installation consists of setting up the following components:
1. Packages / Dependencies
2. Ruby
3. System Users
-4. Gitolite
+4. GitLab shell
5. Database
6. GitLab
7. Nginx
@@ -32,32 +33,31 @@ The GitLab installation consists of setting up the following components:
# 1. Packages / Dependencies
-`sudo` is not installed on Debian by default. If you don't have it you'll need
-to install it first.
+`sudo` is not installed on Debian by default. Make sure your system is
+up-to-date and install it.
- # run as root
- apt-get update && apt-get upgrade && apt-get install sudo
-
-Make sure your system is up-to-date:
-
- sudo apt-get update
- sudo apt-get upgrade
+ # run as root!
+ apt-get update -y
+ apt-get upgrade -y
+ apt-get install sudo -y
**Note:**
-Vim is an editor that is used here whenever there are files that need to be
-edited by hand. But, you can use any editor you like instead.
+During this installation some files will need to be edited manually.
+If you are familiar with vim set it as default editor with the commands below.
+If you are not familiar with vim please skip this and keep using the default editor.
- # Install vim
+ # Install vim and set as default editor
sudo apt-get install -y vim
+ sudo update-alternatives --set editor /usr/bin/vim.basic
Install the required packages:
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl git-core openssh-server redis-server postfix checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev
+ sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl git-core openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev
Make sure you have the right version of Python installed.
# Install Python
- sudo apt-get install python
+ sudo apt-get install -y python
# Make sure that Python is 2.5+ (3.x is not supported at the moment)
python --version
@@ -71,21 +71,35 @@ Make sure you have the right version of Python installed.
# If you get a "command not found" error create a link to the python binary
sudo ln -s /usr/bin/python /usr/bin/python2
+ # For reStructuredText markup language support install required package:
+ sudo apt-get install python-docutils
+
+**Note:** In order to receive mail notifications, make sure to install a
+mail server. By default, Debian is shipped with exim4 whereas Ubuntu
+does not ship with one. The recommended mail server is postfix and you can install it with:
+
+ sudo apt-get install -y postfix
+
+Then select 'Internet Site' and press enter to confirm the hostname.
# 2. Ruby
-Download and compile it:
+Remove the old Ruby 1.8 if present
+
+ sudo apt-get remove ruby1.8
+
+Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
- curl --progress http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p327.tar.gz | tar xz
- cd ruby-1.9.3-p327
+ curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p247.tar.gz | tar xz
+ cd ruby-2.0.0-p247
./configure
make
sudo make install
Install the Bundler Gem:
- sudo gem install bundler
+ sudo gem install bundler --no-ri --no-rdoc
# 3. System Users
@@ -94,27 +108,35 @@ Create a `git` user for Gitlab:
sudo adduser --disabled-login --gecos 'GitLab' git
+
# 4. GitLab shell
- # login as git
- sudo su git
+GitLab Shell is an ssh access and repository management software developed specially for GitLab.
- # go to home directory
+ # Go to home directory
cd /home/git
- # clone gitlab shell
- git clone https://github.com/gitlabhq/gitlab-shell.git
+ # Clone gitlab shell
+ sudo -u git -H git clone https://github.com/gitlabhq/gitlab-shell.git
- # setup
cd gitlab-shell
- cp config.yml.example config.yml
- ./bin/install
+ # switch to right version
+ sudo -u git -H git checkout v1.7.1
+
+ sudo -u git -H cp config.yml.example config.yml
+
+ # Edit config and replace gitlab_url
+ # with something like 'http://domain.com/'
+ sudo -u git -H editor config.yml
+
+ # Do setup
+ sudo -u git -H ./bin/install
# 5. Database
-To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install/databases.md`](./databases.md) .
+To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install/databases.md`](./databases.md).
# 6. GitLab
@@ -127,15 +149,14 @@ To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install
# Clone GitLab repository
sudo -u git -H git clone https://github.com/gitlabhq/gitlabhq.git gitlab
- # Go to gitlab dir
+ # Go to gitlab dir
cd /home/git/gitlab
-
+
# Checkout to stable release
- sudo -u git -H git checkout 5-0-stable
+ sudo -u git -H git checkout 6-1-stable
**Note:**
-You can change `5-0-stable` to `master` if you want the *bleeding edge* version, but
-do so with caution!
+You can change `6-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
## Configure it
@@ -146,7 +167,7 @@ do so with caution!
# Make sure to change "localhost" to the fully-qualified domain name of your
# host serving GitLab where necessary
- sudo -u git -H vim config/gitlab.yml
+ sudo -u git -H editor config/gitlab.yml
# Make sure GitLab can write to the log/ and tmp/ directories
sudo chown -R git log/
@@ -154,48 +175,83 @@ do so with caution!
sudo chmod -R u+rwX log/
sudo chmod -R u+rwX tmp/
- # Make directory for satellites
+ # Create directory for satellites
sudo -u git -H mkdir /home/git/gitlab-satellites
+ # Create directories for sockets/pids and make sure GitLab can write to them
+ sudo -u git -H mkdir tmp/pids/
+ sudo -u git -H mkdir tmp/sockets/
+ sudo chmod -R u+rwX tmp/pids/
+ sudo chmod -R u+rwX tmp/sockets/
+
+ # Create public/uploads directory otherwise backup will fail
+ sudo -u git -H mkdir public/uploads
+ sudo chmod -R u+rwX public/uploads
+
# Copy the example Unicorn config
sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb
+ # Enable cluster mode if you expect to have a high load instance
+ # Ex. change amount of workers to 3 for 2GB RAM server
+ sudo -u git -H editor config/unicorn.rb
+
+ # Configure Git global settings for git user, useful when editing via web
+ # Edit user.email according to what is set in gitlab.yml
+ sudo -u git -H git config --global user.name "GitLab"
+ sudo -u git -H git config --global user.email "gitlab@localhost"
+ sudo -u git -H git config --global core.autocrlf input
+
**Important Note:**
-Make sure to edit both files to match your setup.
+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
+ or
+
# PostgreSQL
sudo -u git cp config/database.yml.postgresql config/database.yml
-Make sure to update username/password in config/database.yml.
+ # Make sure to update username/password in config/database.yml.
+ # You only need to adapt the production settings (first part).
+ # If you followed the database guide then please do as follows:
+ # Change 'root' to 'gitlab'
+ # Change 'secure password' with the value you have given to $password
+ # You can keep the double quotes around the password
+ sudo -u git -H editor config/database.yml
+
+ # Make config/database.yml readable to git only
+ sudo -u git -H chmod o-rwx config/database.yml
## Install Gems
cd /home/git/gitlab
- sudo gem install charlock_holmes --version '0.6.9'
+ sudo gem install charlock_holmes --version '0.6.9.4'
- # For MySQL (note, the option says "without")
- sudo -u git -H bundle install --deployment --without development test postgres
+ # For MySQL (note, the option says "without ... postgres")
+ sudo -u git -H bundle install --deployment --without development test postgres aws
- # Or for PostgreSQL
- sudo -u git -H bundle install --deployment --without development test mysql
+ # Or for PostgreSQL (note, the option says "without ... mysql")
+ sudo -u git -H bundle install --deployment --without development test mysql aws
-## Initialise Database and Activate Advanced Features
+## Initialize Database and Activate Advanced Features
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
+ # Type 'yes' to create the database.
+
+ # When done you see 'Administrator account created:'
+
## Install Init Script
Download the init script (will be /etc/init.d/gitlab):
- sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlab-recipes/master/init.d/gitlab
+ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
sudo chmod +x /etc/init.d/gitlab
Make GitLab start on boot:
@@ -205,10 +261,18 @@ Make GitLab start on boot:
## Check Application Status
-Check if GitLab and its environment is configured correctly:
+Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+## Start Your GitLab Instance
+
+ sudo service gitlab start
+ # or
+ sudo /etc/init.d/gitlab restart
+
+## Double-check Application Status
+
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
@@ -216,39 +280,32 @@ To make sure you didn't miss anything run a more thorough check with:
If all items are green, then congratulations on successfully installing GitLab!
However there are still a few steps left.
-## Start Your GitLab Instance
-
- sudo service gitlab start
- # or
- sudo /etc/init.d/gitlab restart
-
# 7. Nginx
**Note:**
-If you can't or don't want to use Nginx as your web server, have a look at the
-"Advanced Setup Tips" section.
+Nginx is the officially supported web server for GitLab. If you cannot or do not want to use Nginx as your web server, have a look at the
+[GitLab recipes](https://github.com/gitlabhq/gitlab-recipes).
## Installation
- sudo apt-get install nginx
+ sudo apt-get install -y nginx
## Site Configuration
Download an example site config:
- sudo curl --output /etc/nginx/sites-available/gitlab https://raw.github.com/gitlabhq/gitlab-recipes/master/nginx/gitlab
+ sudo cp lib/support/nginx/gitlab /etc/nginx/sites-available/gitlab
sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab
Make sure to edit the config file to match your setup:
- # Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN**
- # to the IP address and fully-qualified domain name
- # of your host serving GitLab
- sudo vim /etc/nginx/sites-enabled/gitlab
+ # Change YOUR_SERVER_FQDN to the fully-qualified
+ # domain name of your host serving GitLab.
+ sudo editor /etc/nginx/sites-available/gitlab
## Restart
- sudo /etc/init.d/nginx restart
+ sudo service nginx restart
# Done!
@@ -260,7 +317,7 @@ The setup has created an admin account for you. You can use it to log in:
5iveL!fe
**Important Note:**
-Please go over to your profile page and immediately chage the password, so
+Please go over to your profile page and immediately change the password, so
nobody can access your GitLab by using this login information later on.
**Enjoy!**
@@ -278,12 +335,12 @@ a different host, you can configure its connection string via the
`config/resque.yml` file.
# example
- production: redis.example.tld:6379
+ production: redis://redis.example.tld:6379
## Custom SSH Connection
-If you are running SSH on a non-standard port, you must change the gitlab user'S SSH config.
-
+If you are running SSH on a non-standard port, you must change the gitlab user's SSH config.
+
# Add to /home/git/.ssh/config
host localhost # Give your setup a name (here: override localhost)
user git # Your remote git user
@@ -292,7 +349,39 @@ If you are running SSH on a non-standard port, you must change the gitlab user'S
You also need to change the corresponding options (e.g. ssh_user, ssh_host, admin_uri) in the `config\gitlab.yml` file.
-## User-contributed Configurations
+## LDAP authentication
+
+You can configure LDAP authentication in config/gitlab.yml. Please restart GitLab after editing this file.
+
+## Using Custom Omniauth Providers
+
+GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already ships with a few providers preinstalled (e.g. LDAP, GitHub, Twitter). But sometimes that is not enough and you need to integrate with other authentication solutions. For these cases you can use the Omniauth provider.
+
+### Steps
+
+These steps are fairly general and you will need to figure out the exact details from the Omniauth provider's documentation.
+
+* Stop GitLab
+ `sudo service gitlab stop`
+
+* Add provider specific configuration options to your `config/gitlab.yml` (you can use the [auth providers section of the example config](https://github.com/gitlabhq/gitlabhq/blob/master/config/gitlab.yml.example) as a reference)
+
+* Add the gem to your [Gemfile](https://github.com/gitlabhq/gitlabhq/blob/master/Gemfile)
+ `gem "omniauth-your-auth-provider"`
+* If you're using MySQL, install the new Omniauth provider gem by running the following command:
+ `sudo -u git -H bundle install --without development test postgres --path vendor/bundle --no-deployment`
+
+* If you're using PostgreSQL, install the new Omniauth provider gem by running the following command:
+ `sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment`
+
+> These are the same commands you used in the [Install Gems section](#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`.
+
+* Start GitLab
+ `sudo service gitlab start`
+
+
+### Examples
-You can find things like AWS installation scripts, init scripts or config files
-for alternative web server in our [recipes collection](https://github.com/gitlabhq/gitlab-recipes/).
+If you have successfully set up a provider that is not shipped with GitLab itself, please let us know.
+You can help others by reporting successful configurations and probably share a few insights or provide warnings for common errors or pitfalls by sharing your experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Working-Custom-Omniauth-Provider-Configurations).
+While we can't officially support every possible auth mechanism out there, we'd like to at least help those with special needs.
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index ec5b013c5d8..1dba04f4237 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -1,13 +1,3 @@
-# Hardware
-
-We recommend you to run GitLab on a server with at least 1GB RAM.
-
-The necessary hard disk space largely depends on the size of the repos you want
-to use GitLab with. But as a *rule of thumb* you should have at least as much
-free space as your all repos combined take up.
-
-
-
# Operating Systems
## Linux
@@ -36,8 +26,7 @@ systems. This means you may get it to work on systems running FreeBSD or OS X.
## Windows
GitLab does **not** run on Windows and we have no plans of supporting it in the
-near future.
-
+near future. Please consider using a virtual machine to run GitLab.
# Rubies
@@ -48,9 +37,30 @@ While it is generally possible to use other Rubies (like
some work on your part.
+# Hardware requirements
+
+## CPU
+
+We recommend a processor with **4 cores**. At a minimum you need a processor with 2 cores to responsively run an unmodified installation.
+
+## Memory
+
+- 512MB is too little memory, GitLab will be very slow and you will need 250MB of swap
+- 768MB is the minimal memory size and supports up to 100 users
+- **1GB** is the **recommended** memory size and supports up to 1,000 users
+- 1.5GB supports up to 10,000 users
+
+## Storage
+
+The necessary hard drive space largely depends on the size of the repos you want
+to store in GitLab. But as a *rule of thumb* you should have at least twice as much
+free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume.
+
+If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
+
# Installation troubles and reporting success or failure
-If you have troubles installing GitLab following the official installation guide
+If you have troubles installing GitLab following the [official installation guide](installation.md)
or want to share your experience installing GitLab on a not officially supported
-platform, please follow the the contribution guide (see CONTRIBUTING.md).
+platform, please follow the the [contribution guide](/CONTRIBUTING.md).
diff --git a/doc/make_release.md b/doc/make_release.md
new file mode 100644
index 00000000000..5be0d5980a1
--- /dev/null
+++ b/doc/make_release.md
@@ -0,0 +1,56 @@
+# Things to do when creating new release
+NOTE: This is a developer guide. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update).
+## Install guide up to date?
+
+* References correct GitLab branch `x-x-stable` and correct GitLab shell tag?
+
+## Make upgrade guide
+
+### From x.x to x.x
+
+#### 0. Any major changes? Database updates? Web server change? File structure changes?
+
+#### 1. Make backup
+
+#### 2. Stop server
+
+#### 3. Do users need to update dependencies like `git`?
+
+#### 4. Get latest code
+
+#### 5. Does GitLab shell need to be updated?
+
+#### 6. Install libs, migrations, etc.
+
+#### 7. Any config files updated since last release?
+
+Check if any of these changed since last release (~22nd of last month depending on when last release branch was created):
+
+* https://github.com/gitlabhq/gitlabhq/commits/master/lib/support/nginx/gitlab
+* https://github.com/gitlabhq/gitlab-shell/commits/master/config.yml.example
+* https://github.com/gitlabhq/gitlabhq/commits/master/config/gitlab.yml.example
+* https://github.com/gitlabhq/gitlabhq/commits/master/config/unicorn.rb.example
+* https://github.com/gitlabhq/gitlabhq/commits/master/config/database.yml.mysql
+* https://github.com/gitlabhq/gitlabhq/commits/master/config/database.yml.postgresql
+
+#### 8. Need to update init script?
+
+Check if changed since last release (~22nd of last month depending on when last release branch was created): https://github.com/gitlabhq/gitlabhq/commits/master/lib/support/init.d/gitlab
+
+#### 9. Start application
+
+#### 10. Check application status
+
+## Make sure code status is good
+
+* [![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://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) on travis-ci.org (master branch)
+
+* [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
+
+* [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available)
+
+* [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
+
+## Make release branch
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
new file mode 100644
index 00000000000..a84222f9fc2
--- /dev/null
+++ b/doc/markdown/markdown.md
@@ -0,0 +1,472 @@
+----------------------------------------------
+
+Table of Contents
+=================
+
+----------------------------------------------
+
+[GitLab Flavored Markdown](#toc_3)
+-------------------------------
+[Newlines](#toc_4)
+[Multiple underscores in words](#toc_5)
+[URL autolinking](#toc_6)
+[Code and Syntax Highlighting](#toc_7)
+[Emoji](#toc_8)
+[Special GitLab references](#toc_9)
+
+
+
+[Standard Markdown](#toc_10)
+------------------------------
+[Headers](#toc_11)
+[Emphasis](#toc_20)
+[Lists](#toc_21)
+[Links](#toc_22)
+[Images](#toc_23)
+[Blockquotes](#toc_24)
+[Inline HTML](#toc_25)
+[Horizontal Rule](#toc_26)
+[Line Breaks](#toc_27)
+[Tables](#toc_28)
+
+[References](#toc_29)
+---------------------
+
+----------------------------------------------
+
+<a name="gfm" />
+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.
+
+You can use GFM in
+
+* commit messages
+* comments
+* wall posts
+* issues
+* merge requests
+* milestones
+* wiki pages
+
+<a name="newlines" />
+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.
+
+The next paragraph contains two phrases separated by a single newline character:
+
+ Roses are red
+ Violets are blue
+
+Roses are red
+Violets are blue
+
+<a name="underscores" />
+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.
+
+ perform_complicated_task
+ do_this_and_do_that_and_another_thing
+
+perform_complicated_task
+do_this_and_do_that_and_another_thing
+
+<a name="autolink" />
+URL autolinking
+---------------
+GFM will autolink standard URLs you copy and paste into your text.
+So if you want to link to a URL (instead of a textural link), you can simply put the URL in verbatim and it will be turned into a link to that URL.
+
+ http://www.google.com
+
+http://www.google.com
+
+<a name="code"/>
+## Code and Syntax Highlighting
+
+Blocks of code are either fenced by lines with three back-ticks <code>```</code>, or are indented with four spaces. Only the fenced code blocks support syntax highlighting.
+
+
+```no-highlight
+Inline `code` has `back-ticks around` it.
+```
+
+Inline `code` has `back-ticks around` it.
+
+Example:
+
+ ```javascript
+ var s = "JavaScript syntax highlighting";
+ alert(s);
+ ```
+
+ ```python
+ def function():
+ #indenting works just fine in the fenced code block
+ s = "Python syntax highlighting"
+ print s
+ ```
+
+ ```ruby
+ require 'redcarpet'
+ markdown = Redcarpet.new("Hello World!")
+ puts markdown.to_html
+ ```
+
+ ```
+ No language indicated, so no syntax highlighting.
+ s = "There is no highlighting for this."
+ But let's throw in a <b>tag</b>.
+ ```
+
+becomes:
+
+```javascript
+var s = "JavaScript syntax highlighting";
+alert(s);
+```
+
+```python
+def function():
+ #indenting works just fine in the fenced code block
+ s = "Python syntax highlighting"
+ print s
+```
+
+```ruby
+require 'redcarpet'
+markdown = Redcarpet.new("Hello World!")
+puts markdown.to_html
+```
+
+```
+No language indicated, so no syntax highlighting.
+s = "There is no highlighting for this."
+But let's throw in a <b>tag</b>.
+```
+
+<a name="emoji"/>
+Emoji
+-----
+
+ Sometimes you want to be :cool: and add some :sparkles: to your :speech_balloon:. Well we have a :gift: for you:
+
+ :exclamation: You can use emoji anywhere GFM is supported. :sunglasses:
+
+ You can use it to point out a :bug: or warn about :monkey:patches. And if someone improves your really :snail: code, send them a :bouquet: or some :candy:. People will :heart: you for that.
+
+ If you are :new: to this, don't be :fearful:. You can easily join the emoji :circus_tent:. All you need to do is to :book: up on the supported codes.
+
+ Consult the [Emoji Cheat Sheet](http://www.emoji-cheat-sheet.com/) for a list of all supported emoji codes. :thumbsup:
+
+Sometimes you want to be :cool: and add some :sparkles: to your :speech_balloon:. Well we have a :gift: for you:
+
+:exclamation: You can use emoji anywhere GFM is supported. :sunglasses:
+
+You can use it to point out a :bug: or warn about :monkey:patches. And if someone improves your really :snail: code, send them a :bouquet: or some :candy:. People will :heart: you for that.
+
+If you are :new: to this, don't be :fearful:. You can easily join the emoji :circus_tent:. All you need to do is to :book: up on the supported codes.
+
+Consult the [Emoji Cheat Sheet](http://www.emoji-cheat-sheet.com/) for a list of all supported emoji codes. :thumbsup:
+
+<a name="special"/>
+Special GitLab References
+-----
+
+GFM recognized special references.
+You can easily reference e.g. a team member, an issue, or a commit within a project.
+GFM will turn that reference into a link so you can navigate between them easily.
+
+
+GFM will recognize the following:
+
+* @foo : for team members
+* #123 : for issues
+* !123 : for merge requests
+* $123 : for snippets
+* 1234567 : for commits
+* [file](path/to/file) : for file references
+
+<a name="standard"/>
+
+----------------------------------
+# Standard Markdown
+
+----------------------------------
+<a name="headers"/>
+## Headers
+
+```no-highlight
+# H1
+## H2
+### H3
+#### H4
+##### H5
+###### H6
+
+Alternatively, for H1 and H2, an underline-ish style:
+
+Alt-H1
+======
+
+Alt-H2
+------
+```
+
+# H1
+## H2
+### H3
+#### H4
+##### H5
+###### H6
+
+Alternatively, for H1 and H2, an underline-ish style:
+
+Alt-H1
+======
+
+Alt-H2
+------
+
+<a name="emphasis"/>
+## Emphasis
+
+```no-highlight
+Emphasis, aka italics, with *asterisks* or _underscores_.
+
+Strong emphasis, aka bold, with **asterisks** or __underscores__.
+
+Combined emphasis with **asterisks and _underscores_**.
+
+Strikethrough uses two tildes. ~~Scratch this.~~
+```
+
+Emphasis, aka italics, with *asterisks* or _underscores_.
+
+Strong emphasis, aka bold, with **asterisks** or __underscores__.
+
+Combined emphasis with **asterisks and _underscores_**.
+
+Strikethrough uses two tildes. ~~Scratch this.~~
+
+
+<a name="lists"/>
+## Lists
+
+```no-highlight
+1. First ordered list item
+2. Another item
+ * Unordered sub-list.
+1. Actual numbers don't matter, just that it's a number
+ 1. Ordered sub-list
+4. And another item.
+
+ Some text that should be aligned with the above item.
+
+* Unordered list can use asterisks
+- Or minuses
++ Or pluses
+```
+
+1. First ordered list item
+2. Another item
+ * Unordered sub-list.
+1. Actual numbers don't matter, just that it's a number
+ 1. Ordered sub-list
+4. And another item.
+
+ Some text that should be aligned with the above item.
+
+* Unordered list can use asterisks
+- Or minuses
++ Or pluses
+
+<a name="links"/>
+## Links
+
+There are two ways to create links.
+
+ [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)
+
+ [You can use numbers for reference-style link definitions][1]
+
+ Or leave it empty and use the [link text itself][]
+
+ Some text to show that the reference links can follow later.
+
+ [arbitrary case-insensitive reference text]: https://www.mozilla.org
+ [1]: http://slashdot.org
+ [link text itself]: http://www.reddit.com
+
+[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)
+
+[You can use numbers for reference-style link definitions][1]
+
+Or leave it empty and use the [link text itself][]
+
+Some text to show that the reference links can follow later.
+
+[arbitrary case-insensitive reference text]: https://www.mozilla.org
+[1]: http://slashdot.org
+[link text itself]: http://www.reddit.com
+
+<a name="images"/>
+## Images
+
+ Here's our logo (hover to see the title text):
+
+ Inline-style:
+ ![alt text](/assets/logo-white.png "Logo Title Text 1")
+
+ Reference-style:
+ ![alt text][logo]
+
+ [logo]: /assets/logo-white.png "Logo Title Text 2"
+
+Here's our logo (hover to see the title text):
+
+Inline-style:
+![alt text](/assets/logo-white.png "Logo Title Text 1")
+
+Reference-style:
+![alt text][logo]
+
+[logo]: /assets/logo-white.png "Logo Title Text 2"
+
+<a name="blockquotes"/>
+## Blockquotes
+
+```no-highlight
+> Blockquotes are very handy in email to emulate reply text.
+> This line is part of the same quote.
+
+Quote break.
+
+> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
+```
+
+> Blockquotes are very handy in email to emulate reply text.
+> This line is part of the same quote.
+
+Quote break.
+
+> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
+
+<a name="html"/>
+## Inline HTML
+
+You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
+
+```no-highlight
+<dl>
+ <dt>Definition list</dt>
+ <dd>Is something people use sometimes.</dd>
+
+ <dt>Markdown in HTML</dt>
+ <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
+</dl>
+```
+
+<dl>
+ <dt>Definition list</dt>
+ <dd>Is something people use sometimes.</dd>
+
+ <dt>Markdown in HTML</dt>
+ <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
+</dl>
+
+<a name="hr"/>
+## Horizontal Rule
+
+```
+Three or more...
+
+---
+
+Hyphens
+
+***
+
+Asterisks
+
+___
+
+Underscores
+```
+
+Three or more...
+
+---
+
+Hyphens
+
+***
+
+Asterisks
+
+___
+
+Underscores
+
+<a name="lines"/>
+## Line Breaks
+
+My basic recommendation for learning how line breaks work is to experiment and discover -- hit &lt;Enter&gt; once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend.
+
+Here are some things to try out:
+
+```
+Here's a line for us to start with.
+
+This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
+
+This line is also a separate paragraph, but...
+This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
+```
+
+Here's a line for us to start with.
+
+This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
+
+This line is also begins a separate paragraph, but...
+This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
+
+
+<a name="tables"/>
+## Tables
+
+Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them.
+
+```
+| header 1 | header 2 |
+| -------- | -------- |
+| cell 1 | cell 2 |
+| cell 3 | cell 4 |
+```
+
+Code above produces next output:
+
+| header 1 | header 2 |
+| -------- | -------- |
+| cell 1 | cell 2 |
+| cell 3 | cell 4 |
+
+
+------------
+
+<a name="references"/>
+## References
+
+* This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
+* The [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown.
+* [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 9b42afa7ca0..d2da64f3d3c 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -31,7 +31,6 @@ Dumping database tables:
- Dumping table wikis... [DONE]
Dumping repositories:
- Dumping repository abcd... [DONE]
-- Dumping repository gitolite-admin.git... [DONE]
Creating backup archive: $TIMESTAMP_gitlab_backup.tar [DONE]
Deleting tmp directories...[DONE]
Deleting old backups... [SKIPPING]
@@ -77,6 +76,5 @@ Restoring database tables:
- Loading fixture wikis...[SKIPPING]
Restoring repositories:
- Restoring repository abcd... [DONE]
-- Restoring repository gitolite-admin.git... [DONE]
Deleting tmp directories...[DONE]
```
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index ad9e5a613b0..99809ef434d 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -1,18 +1,12 @@
-### Remove grabage from gitolite config and filesystem. Important! Data loss!
+### Remove garbage from filesystem. Important! Data loss!
-Remove projects from gitolite config if they dont exist in GitLab database
-
-```
-bundle exec rake gitlab:cleanup:config RAILS_ENV=production
-```
-
-Remove namespaces(dirs) from /home/git/repositories if they dont exist in GitLab database
+Remove namespaces(dirs) from `/home/git/repositories` if they don't exist in GitLab database.
```
bundle exec rake gitlab:cleanup:dirs RAILS_ENV=production
```
-Remove repositories (global only for now) from /home/git/repositories if they dont exist in GitLab database
+Remove repositories (global only for now) from `/home/git/repositories` if they don't exist in GitLab database.
```
bundle exec rake gitlab:cleanup:repos RAILS_ENV=production
diff --git a/doc/raketasks/features.md b/doc/raketasks/features.md
index 7f7daaf046c..018817d219b 100644
--- a/doc/raketasks/features.md
+++ b/doc/raketasks/features.md
@@ -17,12 +17,12 @@ bundle exec rake gitlab:enable_namespaces RAILS_ENV=production
```
-### Enable auto merge
+### Rebuild project satellites
-This command will enable the auto merge feature. After this you will be able to **merge a merge request** via GitLab and use the **online editor**.
+This command will build missing satellites for projects. After this you will be able to **merge a merge request** via GitLab and use the **online editor**.
```
-bundle exec rake gitlab:enable_automerge RAILS_ENV=production
+bundle exec rake gitlab:satellites:create RAILS_ENV=production
```
Example output:
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index 110dbd161f7..3033d8c46b4 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -11,33 +11,30 @@ Example output:
```
System information
-System: Debian 6.0.6
-Current User: gitlab
-Using RVM: yes
-RVM Version: 1.17.2
-Ruby Version: ruby-1.9.3-p327
-Gem Version: 1.8.24
-Bundler Version:1.2.3
-Rake Version: 10.0.1
+System: Debian 6.0.7
+Current User: git
+Using RVM: no
+Ruby Version: 1.9.3p392
+Gem Version: 1.8.23
+Bundler Version:1.3.5
+Rake Version: 10.0.4
GitLab information
-Version: 3.1.0
-Resivion: fd5141d
-Directory: /home/gitlab/gitlab
-DB Adapter: mysql2
-URL: http://localhost:3000
-HTTP Clone URL: http://localhost:3000/some-project.git
-SSH Clone URL: git@localhost:some-project.git
-Using LDAP: no
-Using Omniauth: no
-
-Gitolite information
-Version: v3.04-4-g4524f01
-Admin URI: git@localhost:gitolite-admin
-Admin Key: gitlab
-Repositories: /home/git/repositories/
-Hooks: /home/git/.gitolite/hooks/
-Git: /usr/bin/git
+Version: 5.1.0.beta2
+Revision: 4da8b37
+Directory: /home/git/gitlab
+DB Adapter: mysql2
+URL: http://localhost
+HTTP Clone URL: http://localhost/some-project.git
+SSH Clone URL: git@localhost:some-project.git
+Using LDAP: no
+Using Omniauth: no
+
+GitLab Shell
+Version: 1.2.0
+Repositories: /home/git/repositories/
+Hooks: /home/git/gitlab-shell/hooks/
+Git: /usr/bin/git
```
@@ -46,8 +43,8 @@ Git: /usr/bin/git
Runs the following rake tasks:
* gitlab:env:check
-* gitlab:gitolite:check
-* gitlab:resque:check
+* gitlab:gitlab_shell:check
+* gitlab:sidekiq:check
* gitlab:app:check
It will check that each component was setup according to the installation guide and suggest fixes for issues found.
@@ -63,64 +60,43 @@ Example output:
```
Checking Environment ...
-gitlab user is in git group? ... yes
-Has no "-e" in ~git/.profile ... yes
-Git configured for gitlab user? ... yes
+Git configured for git user? ... yes
Has python2? ... yes
python2 is supported version? ... yes
Checking Environment ... Finished
-Checking Gitolite ...
+Checking GitLab Shell ...
-Using recommended version ... yes
-Repo umask is 0007 in .gitolite.rc? ... yes
-Allow all Git config keys in .gitolite.rc ... yes
-Config directory exists? ... yes
-Config directory owned by git:git? ... yes
-Config directory access is drwxr-x---? ... yes
+GitLab Shell version? ... OK (1.2.0)
Repo base directory exists? ... yes
+Repo base directory is a symlink? ... no
Repo base owned by git:git? ... yes
Repo base access is drwxrws---? ... yes
-Can clone gitolite-admin? ... yes
-Can commit to gitolite-admin? ... yes
-post-receive hook exists? ... yes
post-receive hook up-to-date? ... yes
-post-receive hooks in repos are links: ...
-GitLab ... ok
-Non-Ascii Files Test ... ok
-Touch Commit Test ... ok
-Without Master Test ... ok
-Git config in repos: ...
-GitLab ... ok
-Non-Ascii Files Test ... ok
-Touch Commit Test ... ok
-Without Master Test ... ok
+post-receive hooks in repos are links: ... yes
-Checking Gitolite ... Finished
+Checking GitLab Shell ... Finished
-Checking Resque ...
+Checking Sidekiq ...
Running? ... yes
-Checking Resque ... Finished
+Checking Sidekiq ... Finished
Checking GitLab ...
Database config exists? ... yes
-Database is not SQLite ... yes
+Database is SQLite ... no
All migrations up? ... yes
GitLab config exists? ... yes
-GitLab config not outdated? ... yes
+GitLab config outdated? ... no
Log directory writable? ... yes
Tmp directory writable? ... yes
Init script exists? ... yes
Init script up-to-date? ... yes
-Projects have satellites? ...
-GitLab ... yes
-Non-Ascii Files Test ... yes
-Touch Commit Test ... yes
-Without Master Test ... yes
+Projects have satellites? ... yes
+Redis version >= 2.0.0? ... yes
Checking GitLab ... Finished
```
@@ -135,34 +111,18 @@ If necessary, remove the `tmp/repo_satellites` directory and rerun the command b
bundle exec rake gitlab:satellites:create RAILS_ENV=production
```
-
-### Rebuild each key at gitolite config
-
-This will send all users ssh public keys to gitolite and grant them access (based on their permission) to their projects.
-
-```
-bundle exec rake gitlab:gitolite:update_keys RAILS_ENV=production
-```
-
-
-### Rebuild each project at gitolite config
-
-This makes sure that all projects are present in gitolite and can be accessed.
-
-```
-bundle exec rake gitlab:gitolite:update_repos RAILS_ENV=production
-```
-
### Import bare repositories into GitLab project instance
Notes:
* project owner will be a first admin
+* groups will be created as needed
+* group owner will be the first admin
* existing projects will be skipped
How to use:
-1. copy your bare repos under git base_path (see `config/gitlab.yml` git_host -> base_path)
+1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path)
2. run the command below
```
@@ -174,5 +134,8 @@ Example output:
```
Processing abcd.git
* Created abcd (abcd.git)
+Processing group/xyz.git
+ * Created Group group (2)
+ * Created xyz (group/xyz.git)
[...]
```
diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md
index 021ce35931f..8fa2ed1311c 100644
--- a/doc/raketasks/user_management.md
+++ b/doc/raketasks/user_management.md
@@ -1,4 +1,4 @@
-### Add user to as a developer to all projects
+### Add user as a developer to all projects
```
bundle exec rake gitlab:import:user_to_projects[username@domain.tld]
diff --git a/doc/update/2.6-to-3.0.md b/doc/update/2.6-to-3.0.md
new file mode 100644
index 00000000000..d7047d8eb19
--- /dev/null
+++ b/doc/update/2.6-to-3.0.md
@@ -0,0 +1,63 @@
+# From 2.6 to 3.0
+
+### 1. Stop server & resque
+
+ sudo service gitlab stop
+
+### 2. Update code & db
+
+
+```bash
+# Get latest code
+git fetch origin
+git checkout v3.0.3
+
+
+# Install libs
+sudo -u gitlab bundle install --without development test postgres
+
+# update db
+sudo -u gitlab bundle exec rake db:migrate RAILS_ENV=production
+
+# !!! Config should be replaced with a new one. Check it after replace
+cp config/gitlab.yml.example config/gitlab.yml
+
+# update gitolite hooks
+
+# GITOLITE v2:
+sudo cp ./lib/hooks/post-receive /home/git/share/gitolite/hooks/common/post-receive
+sudo chown git:git /home/git/share/gitolite/hooks/common/post-receive
+
+# GITOLITE v3:
+sudo cp ./lib/hooks/post-receive /home/git/.gitolite/hooks/common/post-receive
+sudo chown git:git /home/git/.gitolite/hooks/common/post-receive
+
+# set valid path to hooks in gitlab.yml in git_host section
+# like this
+git_host:
+ # gitolite 2
+ hooks_path: /home/git/share/gitolite/hooks
+ # gitolite 3
+ hooks_path: /home/git/.gitolite/hooks/
+
+
+# Make some changes to gitolite config
+# For more information visit https://github.com/gitlabhq/gitlabhq/pull/1719
+
+# gitolite v2
+sudo -u git -H sed -i 's/\(GL_GITCONFIG_KEYS\s*=>*\s*\).\{2\}/\\1"\.\*"/g' /home/git/.gitolite.rc
+
+# gitlite v3
+sudo -u git -H sed -i "s/\(GIT_CONFIG_KEYS\s*=>*\s*\).\{2\}/\\1'\.\*'/g" /home/git/.gitolite.rc
+
+
+# Check app status
+sudo -u gitlab bundle exec rake gitlab:app:status RAILS_ENV=production
+
+
+```
+
+
+### 3. Start all
+
+ sudo service gitlab start
diff --git a/doc/update/2.9-to-3.0.md b/doc/update/2.9-to-3.0.md
new file mode 100644
index 00000000000..af929e027a4
--- /dev/null
+++ b/doc/update/2.9-to-3.0.md
@@ -0,0 +1,37 @@
+# From 2.9 to 3.0
+
+### 1. Stop server & resque
+
+ sudo service gitlab stop
+
+### 2. Follow instructions
+
+```bash
+
+# Get latest code
+sudo -u gitlab -H git fetch origin
+sudo -u gitlab -H git checkout v3.0.3
+
+# Install gems
+sudo -u gitlab -H bundle install --without development test postgres
+
+# Migrate db
+sudo -u gitlab -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Make some changes to gitolite v3 config
+# For more information visit https://github.com/gitlabhq/gitlabhq/pull/1719
+
+# Gitolite version 3
+sudo -u git -H sed -i "s/\(GIT_CONFIG_KEYS\s*=>*\s*\).\{2\}/\\1'\.\*'/g" /home/git/.gitolite.rc
+
+# If you still use gitolite v2
+sudo -u git -H sed -i 's/\(GL_GITCONFIG_KEYS\s*=>*\s*\).\{2\}/\\1"\.\*"/g' /home/git/.gitolite.rc
+
+# Check APP Status
+sudo -u gitlab -H bundle exec rake gitlab:app:status RAILS_ENV=production
+```
+
+
+### 3. Start all
+
+ sudo service gitlab start
diff --git a/doc/update/3.0-to-3.1.md b/doc/update/3.0-to-3.1.md
new file mode 100644
index 00000000000..5f06f818d10
--- /dev/null
+++ b/doc/update/3.0-to-3.1.md
@@ -0,0 +1,108 @@
+# From 3.0 to 3.1
+
+__IMPORTANT!__
+
+In this release __we moved Resque jobs under own gitlab namespace__.
+
+Despite a lot of advantages it requires from our users to __replace gitolite post-receive hook with new one__.
+
+Most of projects has post-receive file as symlink to gitolite `/home/git/.gitolite/hooks/post-receive`.
+But some of them may have a real file. In this case you should rewrite it with symlink to gitolite hook.
+
+I wrote a bash script which will do it automatically for you. Just make sure all path inside is valid for you
+
+- - -
+
+### 1. Stop server & resque
+
+ sudo service gitlab stop
+
+### 2. Update GitLab
+
+```bash
+
+# Get latest code
+sudo -u gitlab -H git fetch
+sudo -u gitlab -H git checkout v3.1.0
+
+# Install new charlock_holmes
+sudo gem install charlock_holmes --version '0.6.9'
+
+# Install gems for MySQL
+sudo -u gitlab -H bundle install --without development test postgres sqlite
+
+
+# Migrate db
+sudo -u gitlab -H bundle exec rake db:migrate RAILS_ENV=production
+
+
+```
+
+### 3. Update post-receive hooks
+
+#### Gitolite 3
+
+Step 1: Rewrite post-receive hook
+
+```bash
+# Rewrite hook for gitolite 3
+sudo cp ./lib/hooks/post-receive /home/git/.gitolite/hooks/common/post-receive
+sudo chown git:git /home/git/.gitolite/hooks/common/post-receive
+```
+
+Step 2: Rewrite hooks in all projects to symlink gitolite hook
+
+```bash
+# 1. Check for valid path
+sudo -u gitlab -H vim lib/support/rewrite-hooks.sh
+
+# 2. Run script
+sudo -u git -H lib/support/rewrite-hooks.sh
+```
+
+#### Gitolite v2
+
+Step 1: rewrite post-receive hook for gitolite 2
+
+```
+sudo cp ./lib/hooks/post-receive /home/git/share/gitolite/hooks/common/post-receive
+sudo chown git:git /home/git/share/gitolite/hooks/common/post-receive
+```
+
+Step 2: Replace symlinks in project to valid place
+
+
+ #!/bin/bash
+ src="/home/git/repositories"
+ for dir in `ls "$src/"`
+ do
+ if [ -d "$src/$dir" ]; then
+
+ if [ "$dir" = "gitolite-admin.git" ]
+ then
+ continue
+ fi
+
+ project_hook="$src/$dir/hooks/post-receive"
+ gitolite_hook="/home/git/share/gitolite/hooks/common/post-receive"
+
+ ln -s -f $gitolite_hook $project_hook
+ fi
+ done
+
+
+### 4. Check app status
+
+```bash
+
+# Check APP Status
+sudo -u gitlab -H bundle exec rake gitlab:app:status RAILS_ENV=production
+
+
+
+```
+
+
+### 5. Start all
+
+ sudo service gitlab start
diff --git a/doc/update/3.1-to-4.0.md b/doc/update/3.1-to-4.0.md
new file mode 100644
index 00000000000..c5ae3a40a76
--- /dev/null
+++ b/doc/update/3.1-to-4.0.md
@@ -0,0 +1,99 @@
+# From 3.1 to 4.0
+
+## Important changes
+
+* Support for SQLite was dropped
+* Support for gitolite 2 was dropped
+* Projects are organized in namespaces
+* The GitLab post-receive hook needs to be updated
+* The configuration file needs to be updated
+* Availability of `python2` executable
+
+Most of projects has post-receive file as symlink to gitolite `/home/git/.gitolite/hooks/post-receive`.
+But some of them may have a real file. In this case you should rewrite it with symlink to gitolite hook.
+
+I wrote a bash script which will do it automatically for you. Just make sure all path inside is valid for you
+
+- - -
+
+### 1. Stop GitLab & Resque
+
+ sudo service gitlab stop
+
+### 2. Update GitLab
+
+```bash
+
+# Get latest code
+sudo -u gitlab -H git fetch
+sudo -u gitlab -H git checkout 4-0-stable
+
+# Install gems for MySQL
+sudo -u gitlab -H bundle install --without development test postgres
+
+# Update repos permissions
+sudo chmod -R ug+rwXs /home/git/repositories/
+sudo chown -R git:git /home/git/repositories/
+
+# Migrate db
+sudo -u gitlab -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Enable namespaces (**Warning!** All projects in groups will be moved to subdirectories)
+sudo -u gitlab -H bundle exec rake gitlab:enable_namespaces RAILS_ENV=production
+
+```
+
+### 3. Update post-receive hooks (Requires gitolite v3 )
+
+
+Step 1: Rewrite post-receive hook
+
+```bash
+sudo cp ./lib/hooks/post-receive /home/git/.gitolite/hooks/common/post-receive
+sudo chown git:git /home/git/.gitolite/hooks/common/post-receive
+```
+
+Step 2: Update project hooks to be symlinks to the Gitolite hook
+
+```bash
+# 1. Check paths in script
+sudo -u gitlab -H vim lib/support/rewrite-hooks.sh
+
+# 2. Run script
+sudo -u git -H lib/support/rewrite-hooks.sh
+```
+
+
+### 4. Replace config with new one
+
+
+ # backup old one
+ sudo -u gitlab -H cp config/gitlab.yml config/gitlab.yml.old
+
+ # copy new one
+ sudo -u gitlab -H cp config/gitlab.yml.example config/gitlab.yml
+
+ # edit it
+ sudo -u gitlab -H vim config/gitlab.yml
+
+
+### 5. Disable ssh known_host check for own domain
+
+
+ echo "Host localhost
+ StrictHostKeyChecking no
+ UserKnownHostsFile=/dev/null" | sudo tee -a /etc/ssh/ssh_config
+
+ echo "Host YOUR_DOMAIN_NAME
+ StrictHostKeyChecking no
+ UserKnownHostsFile=/dev/null" | sudo tee -a /etc/ssh/ssh_config
+
+
+### 6. Check GitLab's status
+
+ sudo -u gitlab -H bundle exec rake gitlab:check RAILS_ENV=production
+
+
+### 7. Start GitLab & Resque
+
+ sudo service gitlab start
diff --git a/doc/update/4.0-to-4.1.md b/doc/update/4.0-to-4.1.md
new file mode 100644
index 00000000000..324af7597b2
--- /dev/null
+++ b/doc/update/4.0-to-4.1.md
@@ -0,0 +1,57 @@
+# From 4.0 to 4.1
+
+## Important changes
+
+* Resque replaced with Sidekiq
+* New options for configuration file added
+* Init.d script should be updated
+* __requires ruby1.9.3-p327__
+
+- - -
+
+### 1. Stop GitLab & Resque
+
+ sudo service gitlab stop
+
+### 2. Update GitLab
+
+```bash
+# Set the working directory
+cd /home/gitlab/gitlab/
+
+# Get latest code
+sudo -u gitlab -H git fetch
+sudo -u gitlab -H git checkout 4-1-stable
+
+# Install gems for MySQL
+sudo -u gitlab -H bundle install --without development test postgres
+
+# Migrate db
+sudo -u gitlab -H bundle exec rake db:migrate RAILS_ENV=production
+
+```
+
+### 3. Replace init.d script with a new one
+
+```
+# backup old one
+sudo mv /etc/init.d/gitlab /etc/init.d/gitlab.old
+
+# get new one using sidekiq
+sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlab-recipes/4-1-stable/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+
+```
+
+### 4. Check GitLab's status
+
+ sudo -u gitlab -H bundle exec rake gitlab:check RAILS_ENV=production
+
+
+### 5. Start GitLab & Sidekiq
+
+ sudo service gitlab start
+
+### 6. Remove old init.d script
+
+ sudo rm /etc/init.d/gitlab.old
diff --git a/doc/update/4.1-to-4.2.md b/doc/update/4.1-to-4.2.md
new file mode 100644
index 00000000000..536f22415e2
--- /dev/null
+++ b/doc/update/4.1-to-4.2.md
@@ -0,0 +1,38 @@
+# From 4.1 to 4.2
+
+### 1. Stop server & resque
+
+ sudo service gitlab stop
+
+### 2. Update code & db
+
+```bash
+
+#Set the working directory
+cd /home/gitlab/gitlab/
+
+# Get latest code
+sudo -u gitlab git fetch
+
+sudo -u gitlab git checkout 4-2-stable
+
+# Install libs
+sudo -u gitlab bundle install --without development test postgres --deployment
+
+# update db
+sudo -u gitlab bundle exec rake db:migrate RAILS_ENV=production
+
+```
+
+
+### 3. Check GitLab's status
+
+```bash
+sudo -u gitlab -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+
+
+### 4. Start all
+
+ sudo service gitlab start
diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md
new file mode 100644
index 00000000000..053a50ebc88
--- /dev/null
+++ b/doc/update/4.2-to-5.0.md
@@ -0,0 +1,164 @@
+# From 4.2 to 5.0
+
+## Important changes
+
+* We don't use `gitlab` user any more. Everything will be moved to `git` user
+* __requires ruby1.9.3__
+
+
+__0. Stop gitlab__
+
+ sudo service gitlab stop
+
+__1. add bash to git user__
+
+```
+sudo chsh -s /bin/bash git
+```
+
+__2. git clone gitlab-shell__
+
+```
+cd /home/git/
+sudo -u git -H git clone https://github.com/gitlabhq/gitlab-shell.git /home/git/gitlab-shell
+```
+
+__3. setup gitlab-shell__
+
+```bash
+# chmod all repos and files under git
+sudo chown git:git -R /home/git/repositories/
+
+# login as git
+sudo su git
+cd /home/git/gitlab-shell
+
+# copy config
+cp config.yml.example config.yml
+
+# change url to gitlab instance
+# ! make sure url end with '/' like 'https://gitlab.example/'
+vim config.yml
+
+# rewrite hooks
+./support/rewrite-hooks.sh
+
+# check ruby version for git user ( 1.9 required!! )
+# gitlab shell requires system ruby 1.9
+ruby -v
+
+# exit from git user
+exit
+```
+
+__4. Copy gitlab instance to git user__
+
+```bash
+sudo cp -R /home/gitlab/gitlab /home/git/gitlab
+sudo chown git:git -R /home/git/gitlab
+sudo rm -rf /home/gitlab/gitlab-satellites
+
+# if exists
+sudo rm /tmp/gitlab.socket
+```
+
+__5. Update gitlab to recent version__
+
+```bash
+cd /home/git/gitlab
+
+# backup current config
+sudo -u git -H cp config/gitlab.yml config/gitlab.yml.old
+
+sudo -u git -H git fetch
+sudo -u git -H git checkout 5-0-stable
+
+# replace config with recent one
+sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml
+
+# edit it
+sudo -u git -H vim config/gitlab.yml
+
+
+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 gitlab:shell:setup RAILS_ENV=production
+sudo -u git -H bundle exec rake gitlab:shell:build_missing_projects RAILS_ENV=production
+
+sudo -u git -H mkdir /home/git/gitlab-satellites
+sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production
+
+# migrate wiki to git
+sudo -u git -H bundle exec rake gitlab:wiki:migrate RAILS_ENV=production
+
+
+# check permissions for /home/git/.ssh/
+sudo -u git -H chmod 700 /home/git/.ssh
+sudo -u git -H chmod 600 /home/git/.ssh/authorized_keys
+
+# check permissions for /home/git/gitlab/
+sudo chown -R git /home/git/gitlab/log/
+sudo chown -R git /home/git/gitlab/tmp/
+sudo chmod -R u+rwX /home/git/gitlab/log/
+sudo chmod -R u+rwX /home/git/gitlab/tmp/
+sudo -u git -H mkdir /home/git/gitlab/tmp/pids/
+sudo chmod -R u+rwX /home/git/gitlab/tmp/pids
+
+```
+
+__6. Update init.d script and nginx config__
+
+```bash
+# init.d
+sudo rm /etc/init.d/gitlab
+sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlab-recipes/5-0-stable/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+
+# unicorn
+sudo -u git -H cp /home/git/gitlab/config/unicorn.rb /home/git/gitlab/config/unicorn.rb.old
+sudo -u git -H cp /home/git/gitlab/config/unicorn.rb.example /home/git/gitlab/config/unicorn.rb
+
+#nginx
+# Replace path from '/home/gitlab/' to '/home/git/'
+sudo vim /etc/nginx/sites-enabled/gitlab
+sudo service nginx restart
+
+
+```
+
+__7. Start gitlab instance__
+
+```
+
+
+sudo service gitlab start
+
+# check if unicorn and sidekiq started
+# If not try to logout, also check replaced path from '/home/gitlab/' to '/home/git/'
+# in nginx, unicorn, init.d etc
+ps aux | grep unicorn
+ps aux | grep sidekiq
+
+```
+
+__8. Check installation__
+
+
+```bash
+# In 5-10 seconds lets check gitlab-shell
+sudo -u git -H /home/git/gitlab-shell/bin/check
+
+# Example of success output
+# Check GitLab API access: OK
+# Check directories and files:
+# /home/git/repositories: OK
+# /home/git/.ssh/authorized_keys: OK
+
+
+# Now check gitlab instance
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+```
+
+
+__P.S. If everything works as expected you can remove gitlab user from system__
diff --git a/doc/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md
new file mode 100644
index 00000000000..45fc3436ebe
--- /dev/null
+++ b/doc/update/5.0-to-5.1.md
@@ -0,0 +1,68 @@
+# From 5.0 to 5.1
+
+## Release notes:
+
+* `unicorn` replaced with `puma`
+* merge request cached diff will be truncated
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch
+sudo -u git -H git checkout 5-1-stable
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.3.0
+# replace your old config with the new one
+sudo -u git -H mv config.yml config.yml.old
+sudo -u git -H cp config.yml.example config.yml
+# edit options to match old config
+sudo -u git -H vi config.yml
+```
+
+
+### 4. Install libs, migrations etc
+
+```bash
+cd /home/git/gitlab
+sudo rm tmp/sockets/gitlab.socket
+sudo -u git -H cp config/puma.rb.example config/puma.rb
+
+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 migrate_merge_requests RAILS_ENV=production
+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+```
+
+### 5. Update init.d script with a new one
+
+```bash
+# init.d
+sudo rm /etc/init.d/gitlab
+sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlab-recipes/5-1-stable/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+```
+
+### 6. Mysql grant privileges
+
+Only if you are using mysql:
+
+```bash
+mysql -u root -p
+mysql> GRANT LOCK TABLES ON `gitlabhq_production`.* TO 'gitlab'@'localhost';
+mysql> \q
+```
+
+### 7. Start application
+
+ sudo service gitlab start
diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md
new file mode 100644
index 00000000000..8599c4323ea
--- /dev/null
+++ b/doc/update/5.1-to-5.2.md
@@ -0,0 +1,99 @@
+# From 5.1 to 5.2
+
+### 0. Backup
+
+It's useful to make a backup just in case things go south:
+(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch
+sudo -u git -H git checkout 5-2-stable
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.4.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```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
+
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+```
+
+### 5. Update config files
+
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/puma.rb.example but with your settings.
+
+### 6. Update Init script
+
+```bash
+cd /home/git/gitlab
+sudo rm /etc/init.d/gitlab
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+```
+
+### 7. Create uploads directory
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H mkdir public/uploads
+sudo chmod -R u+rwX public/uploads
+```
+
+### 8. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 9. 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 complete!
+
+## Things went south? Revert to previous version (5.1)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 5.0 to 5.1`](5.0-to-5.1.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 RAILS_ENV=production bundle exec rake gitlab:backup:restore
+```
diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md
new file mode 100644
index 00000000000..e00dfa3951a
--- /dev/null
+++ b/doc/update/5.2-to-5.3.md
@@ -0,0 +1,82 @@
+# From 5.2 to 5.3
+
+### 0. Backup
+
+It's useful to make a backup just in case things go south:
+(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch
+sudo -u git -H git checkout 5-3-stable
+```
+
+### 3. Install libs, migrations, etc.
+
+```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
+
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+```
+
+### 4. Update config files
+
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-3-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-3-stable/config/puma.rb.example but with your settings.
+
+### 5. Update Init script
+
+```bash
+sudo rm /etc/init.d/gitlab
+sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/5-3-stable/lib/support/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+```
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. 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 complete!
+
+## Things went south? Revert to previous version (5.2)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 5.1 to 5.2`](5.1-to-5.2.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 RAILS_ENV=production bundle exec rake gitlab:backup:restore
+```
diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md
new file mode 100644
index 00000000000..5fba0e26afa
--- /dev/null
+++ b/doc/update/5.3-to-5.4.md
@@ -0,0 +1,90 @@
+# From 5.3 to 5.4
+
+### 0. Backup
+
+It's useful to make a backup just in case things go south:
+(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch
+sudo -u git -H git checkout 5-4-stable
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.5.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```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
+
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+```
+
+### 5. Update config files
+
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-4-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-4-stable/config/puma.rb.example but with your settings.
+
+### 6. Update Init script
+
+```bash
+sudo rm /etc/init.d/gitlab
+sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/5-4-stable/lib/support/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+```
+
+### 7. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 8. 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 complete!
+
+## Things went south? Revert to previous version (5.3)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 5.2 to 5.3`](5.2-to-5.3.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 RAILS_ENV=production bundle exec rake gitlab:backup:restore
+```
diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md
new file mode 100644
index 00000000000..3b1d9878204
--- /dev/null
+++ b/doc/update/5.4-to-6.0.md
@@ -0,0 +1,113 @@
+# From 5.4 to 6.0
+
+### Deprecations
+
+#### Global projects
+
+The root (global) namespace for projects is deprecated.
+So you need to move all your global projects under groups or users manually before update or they will be automatically moved to the project owner namespace during the update. When a project is moved all its members will receive an email with instructions how to update their git remote url. Please make sure you disable ssending email when you do a test of the upgrade.
+
+#### Teams
+
+We introduce group membership in 6.0 as a replacement for teams.
+The old combination of groups and teams was confusing for a lot of people.
+And when the members of a team where changed this wasn't reflected in the project permissions.
+In GitLab 6.0 you will be able to add members to a group with a permission level for each member.
+These group members will have access to the projects in that group.
+Any changes to group members will immediately be reflected in the project permissions.
+You can even have multiple owners for a group, greatly simplifying administration.
+
+### 0. Backup
+
+It's useful to make a backup just in case things go south:
+(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch
+sudo -u git -H git checkout 6-0-stable
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.7.0
+```
+
+### 4. Install additional packages
+
+```bash
+# For reStructuredText markup language support install required package:
+sudo apt-get install python-docutils
+```
+
+### 5. Install libs, migrations, etc.
+
+```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
+
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+sudo -u git -H bundle exec rake migrate_groups RAILS_ENV=production
+sudo -u git -H bundle exec rake migrate_global_projects RAILS_ENV=production
+sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production
+sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production
+sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production
+
+# Clear redis cache
+sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+
+# Clear and precompile assets
+sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+```
+
+### 6. Update config files
+
+Note: We switched from Puma in GitLab 5.4 to unicorn in GitLab 6.0.
+
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://github.com/gitlabhq/gitlabhq/blob/master/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` the same as https://github.com/gitlabhq/gitlabhq/blob/master/config/unicorn.rb.example but with your settings.
+
+### 7. Update Init script
+
+```bash
+sudo rm /etc/init.d/gitlab
+sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/master/lib/support/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+```
+
+### 8. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 9. 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 complete!
diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md
new file mode 100644
index 00000000000..2fb762e467a
--- /dev/null
+++ b/doc/update/6.0-to-6.1.md
@@ -0,0 +1,102 @@
+# From 6.0 to 6.1
+
+# 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.
+
+### Deprecations
+
+#### Global issue numbers
+
+In 6.1 issue numbers are project specific. This means all issues are renumbered and get a new number in their url. If you use an old issue number url and the issue number does not exist yet you are redirected to the new one. This conversion does not trigger if the old number already exists for this project, this is unlikely but will happen with old issues and large projects.
+
+### 0. Backup
+
+It's useful to make a backup just in case things go south:
+(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H RAILS_ENV=production bundle exec rake gitlab:backup:create
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch
+sudo -u git -H git checkout 6-1-stable
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.7.1
+```
+
+### 4. Install libs, migrations, etc.
+
+```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
+
+
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+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
+```
+
+### 5. Update config files
+
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/6-1-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` same as https://github.com/gitlabhq/gitlabhq/blob/6-1-stable/config/unicorn.rb.example but with your settings.
+
+### 6. Update Init script
+
+```bash
+sudo rm /etc/init.d/gitlab
+sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/6-1-stable/lib/support/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+```
+
+### 7. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 8. 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 complete!
+
+## Things went south? Revert to previous version (6.0)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 5.4 to 6.0`](5.4-to-6.0.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 RAILS_ENV=production bundle exec rake gitlab:backup:restore
+```
diff --git a/features/admin/groups.feature b/features/admin/groups.feature
index 28f35e3a831..6fed9a34869 100644
--- a/features/admin/groups.feature
+++ b/features/admin/groups.feature
@@ -5,12 +5,16 @@ Feature: Admin Groups
And Create gitlab user "John"
And I visit admin groups page
+ Scenario: See group list
+ Then I should be all groups
+
Scenario: Create a group
When I click new group link
And submit form with new group info
Then I should be redirected to group page
And I should see newly created group
+ @javascript
Scenario: Add user into projects in group
When I visit admin group page
When I select user "John" from user list as "Reporter"
diff --git a/features/admin/teams.feature b/features/admin/teams.feature
deleted file mode 100644
index 6a15fddcdcc..00000000000
--- a/features/admin/teams.feature
+++ /dev/null
@@ -1,70 +0,0 @@
-Feature: Admin Teams
- Background:
- Given I sign in as an admin
- And Create gitlab user "John"
-
- Scenario: Create a team
- When I visit admin teams page
- And I click new team link
- And submit form with new team info
- Then I should be redirected to team page
- And I should see newly created team
-
- Scenario: Add user to team
- When I visit admin teams page
- When I have clean "HardCoders" team
- And I visit "HardCoders" team page
- When I click to "Add members" link
- When I select user "John" from user list as "Developer"
- And submit form with new team member info
- Then I should see "John" in teams members list as "Developer"
-
- Scenario: Assign team to existing project
- When I visit admin teams page
- When I have "HardCoders" team with "John" member with "Developer" role
- When I have "Shop" project
- And I visit "HardCoders" team page
- Then I should see empty projects table
- When I click to "Add projects" link
- When I select project "Shop" with max access "Reporter"
- And submit form with new team project info
- Then I should see "Shop" project in projects list
- When I visit "Shop" project admin page
- Then I should see "John" user with role "Reporter" in team table
-
- Scenario: Add user to team with ptojects
- When I visit admin teams page
- When I have "HardCoders" team with "John" member with "Developer" role
- And "HardCoders" team assigned to "Shop" project with "Developer" max role access
- When I have gitlab user "Jimm"
- And I visit "HardCoders" team page
- Then I should see members table without "Jimm" member
- When I click to "Add members" link
- When I select user "Jimm" ub team members list as "Master"
- And submit form with new team member info
- Then I should see "Jimm" in teams members list as "Master"
-
- Scenario: Remove member from team
- Given I have users team "HardCoders"
- And gitlab user "John" is a member "HardCoders" team
- And gitlab user "Jimm" is a member "HardCoders" team
- And "HardCoders" team is assigned to "Shop" project
- When I visit admin teams page
- When I visit "HardCoders" team admin page
- Then I shoould see "John" in members list
- And I should see "Jimm" in members list
- And I should see "Shop" in projects list
- When I click on remove "Jimm" user link
- Then I should be redirected to "HardCoders" team admin page
- And I should not to see "Jimm" user in members list
-
- Scenario: Remove project from team
- Given I have users team "HardCoders"
- And gitlab user "John" is a member "HardCoders" team
- And gitlab user "Jimm" is a member "HardCoders" team
- And "HardCoders" team is assigned to "Shop" project
- When I visit admin teams page
- When I visit "HardCoders" team admin page
- Then I should see "Shop" project in projects list
- When I click on "Relegate" link on "Shop" project
- Then I should see projects liston team page without "Shop" project
diff --git a/features/admin/users.feature b/features/admin/users.feature
index 03ac86a367b..7f503cf9235 100644
--- a/features/admin/users.feature
+++ b/features/admin/users.feature
@@ -6,3 +6,11 @@ Feature: Admin Users
Scenario: On Admin Users
Given I visit admin users page
Then I should see all users
+
+ Scenario: Edit user and change username to non ascii char
+ When I visit admin users page
+ And Click edit
+ And Input non ascii char in username
+ And Click save
+ Then See username error message
+ And Not changed form action url
diff --git a/features/dashboard/active_tab.feature b/features/dashboard/active_tab.feature
index 6715ea269c7..3e1cf5aa0ca 100644
--- a/features/dashboard/active_tab.feature
+++ b/features/dashboard/active_tab.feature
@@ -17,11 +17,6 @@ Feature: Dashboard active tab
Then the active main tab should be Merge Requests
And no other main tabs should be active
- Scenario: On Dashboard Search
- Given I visit dashboard search page
- Then the active main tab should be Search
- And no other main tabs should be active
-
Scenario: On Dashboard Help
Given I visit dashboard help page
Then the active main tab should be Help
diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
index 695148b5cdf..e249f392de7 100644
--- a/features/dashboard/dashboard.feature
+++ b/features/dashboard/dashboard.feature
@@ -16,6 +16,7 @@ Feature: Dashboard
And I visit dashboard page
Then I should see groups list
+ @javascript
Scenario: I should see last push widget
Then I should see last push widget
And I click "Create Merge Request" link
diff --git a/features/dashboard/projects.feature b/features/dashboard/projects.feature
index 17022dab54f..7d8c129face 100644
--- a/features/dashboard/projects.feature
+++ b/features/dashboard/projects.feature
@@ -1,8 +1,8 @@
-Feature: Dashboard
+Feature: Dashboard projects
Background:
Given I sign in as a user
And I own project "Shop"
And I visit dashboard projects page
- Scenario: I should see issues list
+ Scenario: I should see projects list
Then I should see projects list
diff --git a/features/dashboard/search.feature b/features/dashboard/search.feature
index 9813d9d1e7c..91d870f46f3 100644
--- a/features/dashboard/search.feature
+++ b/features/dashboard/search.feature
@@ -2,13 +2,8 @@ Feature: Dashboard Search
Background:
Given I sign in as a user
And I own project "Shop"
- And Project "Shop" has wiki page "Contibuting guide"
And I visit dashboard search page
Scenario: I should see project I am looking for
Given I search for "Sho"
Then I should see "Shop" project link
-
- Scenario: I should see wiki page I am looking for
- Given I search for "Contibuting"
- Then I should see "Contibuting guide" wiki link \ No newline at end of file
diff --git a/features/group/group.feature b/features/group/group.feature
index a48affe8e02..9fec19a4dc1 100644
--- a/features/group/group.feature
+++ b/features/group/group.feature
@@ -19,9 +19,10 @@ Feature: Groups
When I visit group merge requests page
Then I should see merge requests from this group assigned to me
+ @javascript
Scenario: I should add user to projects in Group
Given I have new user "John"
- When I visit group people page
+ When I visit group members page
And I select user "John" from list with role "Reporter"
Then I should see user "John" in team list
diff --git a/features/profile/notifications.feature b/features/profile/notifications.feature
new file mode 100644
index 00000000000..e7937953c1b
--- /dev/null
+++ b/features/profile/notifications.feature
@@ -0,0 +1,8 @@
+Feature: Profile Notifications
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+
+ Scenario: I visit notifications tab
+ When I visit profile notifications page
+ Then I should see global notifications settings
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index 95b85a9f911..c74b0993fb3 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -11,11 +11,25 @@ Feature: Profile
Then I change my contact info
And I should see new contact info
+ Scenario: I change my password without old one
+ Given I visit profile account page
+ When I try change my password w/o old one
+ Then I should see a missing password error message
+ And I should be redirected to account page
+
Scenario: I change my password
Given I visit profile account page
Then I change my password
And I should be redirected to sign in page
+ Scenario: My password is expired
+ Given my password is expired
+ And I am not an ldap user
+ And I visit profile account page
+ Then I redirected to expired password page
+ And I submit new password
+ And I redirected to sign in page
+
Scenario: I unsuccessfully change my password
Given I visit profile account page
When I unsuccessfully change my password
@@ -31,6 +45,11 @@ Feature: Profile
When I visit profile history page
Then I should see my activity
+ Scenario: I visit my user page
+ When I visit profile page
+ And I click on my profile picture
+ Then I should see my user page
+
@javascript
Scenario: I change my application theme
Given I visit profile design page
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 2d3e41d3d33..48c217fbea7 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -49,51 +49,38 @@ Feature: Project active tab
Scenario: On Project Home/Show
Given I visit my project's home page
- Then the active sub tab should be Show
- And no other sub tabs should be active
- And the active main tab should be Home
-
- Scenario: On Project Home/Team
- Given I visit my project's home page
- And I click the "Team" tab
- Then the active sub tab should be Team
- And no other sub tabs should be active
- And the active main tab should be Home
+ Then the active main tab should be Home
+ And no other main tabs should be active
- Scenario: On Project Home/Attachments
- Given I visit my project's home page
- And I click the "Attachments" tab
- Then the active sub tab should be Attachments
- And no other sub tabs should be active
- And the active main tab should be Home
+ # Sub Tabs: Settings
- Scenario: On Project Home/Snippets
- Given I visit my project's home page
- And I click the "Snippets" tab
- Then the active sub tab should be Snippets
- And no other sub tabs should be active
- And the active main tab should be Home
+ Scenario: On Project Settings/Team
+ Given I visit my project's settings page
+ And I click the "Team" tab
+ Then the active sub nav should be Team
+ And no other sub navs should be active
+ And the active main tab should be Settings
- Scenario: On Project Home/Edit
- Given I visit my project's home page
+ Scenario: On Project Settings/Edit
+ Given I visit my project's settings page
And I click the "Edit" tab
- Then the active sub tab should be Edit
- And no other sub tabs should be active
- And the active main tab should be Home
+ Then the active sub nav should be Edit
+ And no other sub navs should be active
+ And the active main tab should be Settings
- Scenario: On Project Home/Hooks
- Given I visit my project's home page
+ Scenario: On Project Settings/Hooks
+ Given I visit my project's settings page
And I click the "Hooks" tab
- Then the active sub tab should be Hooks
- And no other sub tabs should be active
- And the active main tab should be Home
+ Then the active sub nav should be Hooks
+ And no other sub navs should be active
+ And the active main tab should be Settings
- Scenario: On Project Home/Deploy Keys
- Given I visit my project's home page
+ Scenario: On Project Settings/Deploy Keys
+ Given I visit my project's settings page
And I click the "Deploy Keys" tab
- Then the active sub tab should be Deploy Keys
- And no other sub tabs should be active
- And the active main tab should be Home
+ Then the active sub nav should be Deploy Keys
+ And no other sub navs should be active
+ And the active main tab should be Settings
# Sub Tabs: Commits
diff --git a/features/project/commits/commit_diff_comments.feature b/features/project/commits/commit_diff_comments.feature
index 884fab527f5..b26019f832f 100644
--- a/features/project/commits/commit_diff_comments.feature
+++ b/features/project/commits/commit_diff_comments.feature
@@ -83,10 +83,3 @@ Feature: Comments on commit diffs
And I submit the diff comment
Then I should not see the diff comment form
And I should see a discussion reply button
-
-
- #@wip @javascript
- #Scenario: I can delete a discussion comment
- # Given I leave a diff comment like "Typo, please fix"
- # And I delete a diff comment
- # Then I should not see a diff comment saying "Typo, please fix"
diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature
index 56069cdc977..fe470f5ac99 100644
--- a/features/project/commits/commits.feature
+++ b/features/project/commits/commits.feature
@@ -27,3 +27,15 @@ Feature: Project Browse commits
Scenario: I browse commits stats
Given I visit my project's commits stats page
Then I see commits stats
+
+ Scenario: I browse big commit
+ Given I visit big commit page
+ Then I see big commit warning
+
+ Scenario: I browse huge commit
+ Given I visit huge commit page
+ Then I see huge commit message
+
+ Scenario: I browse a commit with an image
+ Given I visit a commit with an image that changed
+ Then The diff links to both the previous and current image
diff --git a/features/project/create_project.feature b/features/project/create_project.feature
index b7cdfdb818e..395a3218b2b 100644
--- a/features/project/create_project.feature
+++ b/features/project/create_project.feature
@@ -9,3 +9,14 @@ Feature: Create Project
And fill project form with valid data
Then I should see project page
And I should see empty project instuctions
+
+ @javascript
+ Scenario: Empty project instructions
+ Given I sign in as a user
+ When I visit new project page
+ And fill project form with valid data
+ Then I see empty project instuctions
+ 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
diff --git a/features/project/deploy_keys.feature b/features/project/deploy_keys.feature
new file mode 100644
index 00000000000..13e3b9bbd2e
--- /dev/null
+++ b/features/project/deploy_keys.feature
@@ -0,0 +1,23 @@
+Feature: Project Deploy Keys
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+
+ Scenario: I should see deploy keys list
+ Given project has deploy key
+ When I visit project deploy keys page
+ Then I should see project deploy keys
+
+ Scenario: I add new deploy key
+ Given I visit project deploy keys page
+ When I click 'New Deploy Key'
+ And I submit new deploy key
+ Then I should be on deploy keys page
+ And I should see newly created deploy key
+
+ Scenario: I attach deploy key to project
+ Given other project has deploy key
+ And I visit project deploy keys page
+ When I click attach deploy key
+ Then I should be on deploy keys page
+ And I should see newly created deploy key
diff --git a/features/project/fork_project.feature b/features/project/fork_project.feature
new file mode 100644
index 00000000000..dc477ca3bf3
--- /dev/null
+++ b/features/project/fork_project.feature
@@ -0,0 +1,14 @@
+Feature: Fork Project
+ Background:
+ Given I sign in as a user
+ And I am a member of project "Shop"
+ When I visit project "Shop" page
+
+ Scenario: User fork a project
+ Given I click link "Fork"
+ Then I should see the forked project page
+
+ Scenario: User already has forked the project
+ Given I already have a project named "Shop" in my namespace
+ And I click link "Fork"
+ Then I should see a "Name has already been taken" warning
diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature
new file mode 100644
index 00000000000..966905645a2
--- /dev/null
+++ b/features/project/forked_merge_requests.feature
@@ -0,0 +1,34 @@
+Feature: Project Forked Merge Requests
+ Background:
+ Given I sign in as a user
+ And I am a member of project "Shop"
+ And I have a project forked off of "Shop" called "Forked Shop"
+
+ @javascript
+ Scenario: I submit new unassigned merge request to a forked project
+ Given I visit project "Forked Shop" merge requests page
+ And I click link "New Merge Request"
+ And I fill out a "Merge Request On Forked Project" merge request
+ And I submit the merge request
+ Then I should see merge request "Merge Request On Forked Project"
+
+ @javascript
+ Scenario: I can edit a forked merge request
+ Given I visit project "Forked Shop" merge requests page
+ And I click link "New Merge Request"
+ And I fill out a "Merge Request On Forked Project" merge request
+ And I submit the merge request
+ And I should see merge request "Merge Request On Forked Project"
+ And I click link edit "Merge Request On Forked Project"
+ Then I see the edit page prefilled for "Merge Request On Forked Project"
+ And I update the merge request title
+ And I save the merge request
+ Then I should see the edited merge request
+
+ @javascript
+ Scenario: I cannot submit an invalid merge request
+ Given I visit project "Forked Shop" merge requests page
+ And I click link "New Merge Request"
+ 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
diff --git a/features/project/graph.feature b/features/project/graph.feature
new file mode 100644
index 00000000000..cda95f5dda6
--- /dev/null
+++ b/features/project/graph.feature
@@ -0,0 +1,9 @@
+Feature: Project Graph
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And I visit project "Shop" graph page
+
+ @javascript
+ Scenario: I should see project graphs
+ Then page should have graphs
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index d6ef384c9a6..67986784bc7 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -3,6 +3,7 @@ Feature: Project Issues
Given I sign in as a user
And I own project "Shop"
And project "Shop" have "Release 0.4" open issue
+ And project "Shop" have "Tweet control" open issue
And project "Shop" have "Release 0.3" closed issue
And I visit project "Shop" issues page
@@ -37,37 +38,20 @@ Feature: Project Issues
@javascript
Scenario: I search issue
- Given I fill in issue search with "Release"
+ Given I fill in issue search with "Re"
Then I should see "Release 0.4" in issues
And I should not see "Release 0.3" in issues
+ And I should not see "Tweet control" in issues
@javascript
Scenario: I search issue that not exist
- Given I fill in issue search with "Bug"
+ Given I fill in issue search with "Bu"
Then I should not see "Release 0.4" in issues
And I should not see "Release 0.3" in issues
-
@javascript
Scenario: I search all issues
Given I click link "All"
- And I fill in issue search with "0.3"
+ And I fill in issue search with ".3"
Then I should see "Release 0.3" in issues
And I should not see "Release 0.4" in issues
-
- # Disable this two cause of random failing
- # TODO: fix after v4.0 released
- #@javascript
- #Scenario: I create Issue with pre-selected milestone
- #Given project "Shop" has milestone "v2.2"
- #And project "Shop" has milestone "v3.0"
- #And I visit project "Shop" issues page
- #When I select milestone "v3.0"
- #And I click link "New Issue"
- #Then I should see selected milestone with title "v3.0"
-
- #@javascript
- #Scenario: I create Issue with pre-selected assignee
- #When I select first assignee from "Shop" project
- #And I click link "New Issue"
- #Then I should see first assignee from "Shop" as selected assignee
diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature
index 50c090cc6a0..2f38acf14d0 100644
--- a/features/project/issues/milestones.feature
+++ b/features/project/issues/milestones.feature
@@ -22,5 +22,3 @@ Feature: Project Milestones
Given the milestone has open and closed issues
And I click link "v2.2"
Then I should see 3 issues
- When I click link "All Issues"
- Then I should see 4 issues
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 5b8becbb6c9..63f27c3acc3 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -29,6 +29,7 @@ Feature: Project Merge Requests
And I click link "Close"
Then I should see closed merge request "Bug NS-04"
+ @javascript
Scenario: I submit new unassigned merge request
Given I click link "New Merge Request"
And I submit new merge request "Wiki Feature"
diff --git a/features/project/network.feature b/features/project/network.feature
index 31ce5ad3279..ceae08c1074 100644
--- a/features/project/network.feature
+++ b/features/project/network.feature
@@ -7,3 +7,34 @@ Feature: Project Network Graph
@javascript
Scenario: I should see project network
Then page should have network graph
+ And page should select "master" in select box
+ And page should have "master" on graph
+
+ @javascript
+ Scenario: I should switch "branch" and "tag"
+ When I switch ref to "stable"
+ Then page should select "stable" in select box
+ And page should have "stable" on graph
+ When I switch ref to "v2.1.0"
+ Then page should select "v2.1.0" in select box
+ And page should have "v2.1.0" on graph
+
+ @javascript
+ Scenario: I should looking for a commit by SHA
+ When I looking for a commit by SHA of "v2.1.0"
+ Then page should have network graph
+ And page should select "master" in select box
+ And page should have "v2.1.0" on graph
+
+ @javascript
+ Scenario: I should filter selected tag
+ When I switch ref to "v2.1.0"
+ Then page should have content not cotaining "v2.1.0"
+ When click "Show only selected branch" checkbox
+ Then page should not have content not cotaining "v2.1.0"
+ When click "Show only selected branch" checkbox
+ Then page should have content not cotaining "v2.1.0"
+
+ Scenario: I should fail to look for a commit
+ When I look for a commit by ";"
+ Then page status code should be 404
diff --git a/features/project/project.feature b/features/project/project.feature
index 23fef69ee48..59eda4a781d 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -5,6 +5,7 @@ Feature: Project Feature
And project "Shop" has push event
And I visit project "Shop" page
+ @javascript
Scenario: I should see project activity
When I visit project "Shop" page
Then I should see project "Shop" activity feed
@@ -18,6 +19,3 @@ Feature: Project Feature
And change project settings
And I save project
Then I should see project with new settings
-
- # @wip
- # Scenario: I visit attachments
diff --git a/features/project/public_projects.feature b/features/project/public_projects.feature
new file mode 100644
index 00000000000..c5a9da14c54
--- /dev/null
+++ b/features/project/public_projects.feature
@@ -0,0 +1,8 @@
+Feature: Public Projects
+ Background:
+ Given I sign in as a user
+
+ Scenario: I should see the list of public projects
+ When I visit the public projects area
+ Then I should see the list of public projects
+
diff --git a/features/project/service.feature b/features/project/service.feature
index ca8a4756056..e685c385d1d 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -12,3 +12,15 @@ Feature: Project Services
And I click gitlab-ci service link
And I fill gitlab-ci settings
Then I should see service settings saved
+
+ Scenario: Activate hipchat service
+ When I visit project "Shop" services page
+ And I click hipchat service link
+ And I fill hipchat settings
+ Then I should see hipchat service settings saved
+
+ Scenario: Activate pivotaltracker service
+ When I visit project "Shop" services page
+ And I click pivotaltracker service link
+ And I fill pivotaltracker settings
+ Then I should see pivotaltracker service settings saved
diff --git a/features/project/snippets.feature b/features/project/snippets.feature
new file mode 100644
index 00000000000..dfaa02663a0
--- /dev/null
+++ b/features/project/snippets.feature
@@ -0,0 +1,35 @@
+Feature: Project Snippets
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And project "Shop" have "Snippet one" snippet
+ And project "Shop" have no "Snippet two" snippet
+ And I visit project "Shop" snippets page
+
+ Scenario: I should see snippets
+ Given I visit project "Shop" snippets page
+ Then I should see "Snippet one" in snippets
+ And I should not see "Snippet two" in snippets
+
+ Scenario: I create new project snippet
+ Given I click link "New Snippet"
+ And I submit new snippet "Snippet three"
+ Then I should see snippet "Snippet three"
+
+ @javascript
+ Scenario: I comment on a snippet "Snippet one"
+ Given I visit snippet page "Snippet one"
+ And I leave a comment like "Good snippet!"
+ Then I should see comment "Good snippet!"
+
+ Scenario: I update "Snippet one"
+ Given I visit snippet page "Snippet one"
+ And I click link "Edit"
+ And I submit new title "Snippet new title"
+ Then I should see "Snippet new title"
+
+ Scenario: I destroy "Snippet one"
+ Given I visit snippet page "Snippet one"
+ And I click link "Edit"
+ And I click link "Remove Snippet"
+ Then I should not see "Snippet one" in snippets
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 0b8495ffc58..ee26f5371a9 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -12,7 +12,7 @@ Feature: Project Browse files
Then I should see files from repository for "8470d70"
Scenario: I browse file content
- Given I click on "Gemfile" file in repo
+ Given I click on "Gemfile.lock" file in repo
Then I should see it content
Scenario: I browse raw file
@@ -22,6 +22,6 @@ Feature: Project Browse files
@javascript
Scenario: I can edit file
- Given I click on "Gemfile" file in repo
+ Given I click on "Gemfile.lock" file in repo
And I click button "edit"
Then I can edit code
diff --git a/features/project/source/git_blame.feature b/features/project/source/git_blame.feature
index 93ed20a8c42..3b20437a875 100644
--- a/features/project/source/git_blame.feature
+++ b/features/project/source/git_blame.feature
@@ -5,6 +5,6 @@ Feature: Project Browse git repo
Given I visit project source page
Scenario: I blame file
- Given I click on "Gemfile" file in repo
+ Given I click on "Gemfile.lock" file in repo
And I click blame button
Then I should see git file blame
diff --git a/features/project/source/search_code.feature b/features/project/source/search_code.feature
new file mode 100644
index 00000000000..13f15cc922f
--- /dev/null
+++ b/features/project/source/search_code.feature
@@ -0,0 +1,9 @@
+Feature: Project Search code
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ Given I visit project source page
+
+ Scenario: Search for term "Welcome to GitLab"
+ When I search for term "Welcome to GitLab"
+ Then I should see files from repository containing "Welcome to GitLab"
diff --git a/features/project/team_management.feature b/features/project/team_management.feature
index 0ac37620b4e..e153978e043 100644
--- a/features/project/team_management.feature
+++ b/features/project/team_management.feature
@@ -11,6 +11,7 @@ Feature: Project Team management
Then I should be able to see myself in team
And I should see "Sam" in team list
+ @javascript
Scenario: Add user to project
Given I click link "New Team Member"
And I select "Mike" as "Reporter"
@@ -20,22 +21,17 @@ Feature: Project Team management
Scenario: Update user access
Given I should see "Sam" in team list as "Developer"
And I change "Sam" role to "Reporter"
- Then I visit project "Shop" team page
And I should see "Sam" in team list as "Reporter"
- Scenario: View team member profile
- Given I click link "Sam"
- Then I should see "Sam" team profile
-
Scenario: Cancel team member
- Given I click link "Sam"
- And I click link "Remove from team"
+ Given I click cancel link for "Sam"
Then I visit project "Shop" team page
And I should not see "Sam" in team list
Scenario: Import team from another project
Given I own project "Website"
And "Mike" is "Website" reporter
+ When I visit project "Shop" team page
And I click link "Import team from another project"
- When I submit "Website" project for import team
+ And I submit "Website" project for import team
Then I should see "Mike" in team list as "Reporter"
diff --git a/features/project/wiki.feature b/features/project/wiki.feature
index f052e2f244c..90eb2b79c66 100644
--- a/features/project/wiki.feature
+++ b/features/project/wiki.feature
@@ -5,5 +5,43 @@ Feature: Project Wiki
Given I visit project wiki page
Scenario: Add new page
- Given I create Wiki page
- Then I should see newly created wiki page
+ Given I create the Wiki Home page
+ Then I should see the newly created wiki page
+
+ Scenario: Pressing Cancel while editing a brand new Wiki
+ Given I click on the Cancel button
+ Then I should be redirected back to the Edit Home Wiki page
+
+ Scenario: Edit existing page
+ Given I have an existing Wiki page
+ And I browse to that Wiki page
+ And I click on the Edit button
+ And I change the content
+ Then I should see the updated content
+
+ Scenario: Pressing Cancel while editing an existing Wiki page
+ Given I have an existing Wiki page
+ And I browse to that Wiki page
+ And I click on the Edit button
+ And I click on the Cancel button
+ Then I should be redirected back to that Wiki page
+
+ Scenario: View page history
+ Given I have an existing wiki page
+ And That page has two revisions
+ And I browse to that Wiki page
+ And I click the History button
+ Then I should see both revisions
+
+ Scenario: Destroy Wiki page
+ Given I have an existing wiki page
+ And I browse to that Wiki page
+ And I click on the Edit button
+ And I click on the "Delete this page" button
+ Then The page should be deleted
+
+ Scenario: View all pages
+ Given I have an existing wiki page
+ And I browse to that Wiki page
+ And I click on the "Pages" button
+ Then I should see the existing page in the pages list
diff --git a/features/public/public_projects.feature b/features/public/public_projects.feature
new file mode 100644
index 00000000000..178a769194c
--- /dev/null
+++ b/features/public/public_projects.feature
@@ -0,0 +1,18 @@
+Feature: Public Projects Feature
+ Background:
+ Given public project "Community"
+ And private project "Enterprise"
+
+ Scenario: I visit public area
+ When I visit the public projects area
+ Then I should see project "Community"
+ And I should not see project "Enterprise"
+
+ Scenario: I visit public project page
+ When I visit project "Community" page
+ Then I should see project "Community" home page
+
+ Scenario: I visit an empty public project page
+ Given public empty project "Empty Public Project"
+ When I visit empty project page
+ Then I should see empty public project details
diff --git a/features/snippets/discover_snippets.feature b/features/snippets/discover_snippets.feature
new file mode 100644
index 00000000000..d6fd2cd7808
--- /dev/null
+++ b/features/snippets/discover_snippets.feature
@@ -0,0 +1,10 @@
+Feature: Discover Snippets
+ Background:
+ Given I sign in as a user
+ And I have public "Personal snippet one" snippet
+ And I have private "Personal snippet private" snippet
+
+ Scenario: I should see snippets
+ Given I visit snippets page
+ Then I should see "Personal snippet one" in snippets
+ And I should not see "Personal snippet private" in snippets
diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature
new file mode 100644
index 00000000000..1119defa17d
--- /dev/null
+++ b/features/snippets/snippets.feature
@@ -0,0 +1,28 @@
+Feature: Snippets Feature
+ Background:
+ Given I sign in as a user
+ And I have public "Personal snippet one" snippet
+ And I have private "Personal snippet private" snippet
+
+ Scenario: I create new snippet
+ Given I visit new snippet page
+ And I submit new snippet "Personal snippet three"
+ Then I should see snippet "Personal snippet three"
+
+ Scenario: I update "Personal snippet one"
+ Given I visit snippet page "Personal snippet one"
+ And I click link "Edit"
+ And I submit new title "Personal snippet new title"
+ Then I should see "Personal snippet new title"
+
+ Scenario: Set "Personal snippet one" public
+ Given I visit snippet page "Personal snippet one"
+ And I click link "Edit"
+ And I uncheck "Private" checkbox
+ Then I should see "Personal snippet one" public
+
+ Scenario: I destroy "Personal snippet one"
+ Given I visit snippet page "Personal snippet one"
+ And I click link "Edit"
+ And I click link "Destroy"
+ Then I should not see "Personal snippet one" in snippets
diff --git a/features/snippets/user_snippets.feature b/features/snippets/user_snippets.feature
new file mode 100644
index 00000000000..4c8a91501c4
--- /dev/null
+++ b/features/snippets/user_snippets.feature
@@ -0,0 +1,22 @@
+Feature: User Snippets
+ Background:
+ Given I sign in as a user
+ And I have public "Personal snippet one" snippet
+ And I have private "Personal snippet private" snippet
+
+ Scenario: I should see all my snippets
+ Given I visit my snippets page
+ Then I should see "Personal snippet one" in snippets
+ And I should see "Personal snippet private" in snippets
+
+ Scenario: I can see only my private snippets
+ Given I visit my snippets page
+ And I click "Private" filter
+ Then I should not see "Personal snippet one" in snippets
+ And I should see "Personal snippet private" in snippets
+
+ Scenario: I can see only my public snippets
+ Given I visit my snippets page
+ And I click "Public" filter
+ Then I should see "Personal snippet one" in snippets
+ And I should not see "Personal snippet private" in snippets
diff --git a/features/steps/admin/admin_groups.rb b/features/steps/admin/admin_groups.rb
index cbca2daa701..b4591f227e3 100644
--- a/features/steps/admin/admin_groups.rb
+++ b/features/steps/admin/admin_groups.rb
@@ -2,6 +2,7 @@ class AdminGroups < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedActiveTab
+ include Select2Helper
When 'I visit admin group page' do
visit admin_group_path(current_group)
@@ -20,16 +21,18 @@ class AdminGroups < Spinach::FeatureSteps
end
And 'Create gitlab user "John"' do
- create(:user, :name => "John")
+ create(:user, name: "John")
end
And 'submit form with new group info' do
- fill_in 'group_name', :with => 'gitlab'
+ fill_in 'group_name', with: 'gitlab'
+ fill_in 'group_description', with: 'Group description'
click_button "Create group"
end
Then 'I should see newly created group' do
page.should have_content "Group: gitlab"
+ page.should have_content "Group description"
end
Then 'I should be redirected to group page' do
@@ -38,17 +41,24 @@ class AdminGroups < Spinach::FeatureSteps
When 'I select user "John" from user list as "Reporter"' do
user = User.find_by_name("John")
+ select2(user.id, from: "#user_ids", multiple: true)
within "#new_team_member" do
- select user.name, :from => "user_ids"
- select "Reporter", :from => "project_access"
+ select "Reporter", from: "group_access"
end
- click_button "Add user to projects in group"
+ click_button "Add users into group"
end
Then 'I should see "John" in team list in every project as "Reporter"' do
- user = User.find_by_name("John")
- projects_with_access = find(".user_#{user.id} .projects_access")
- projects_with_access.should have_link("Reporter")
+ within ".group-users-list" do
+ page.should have_content "John"
+ page.should have_content "Reporter"
+ end
+ end
+
+ step 'I should be all groups' do
+ Group.all.each do |group|
+ page.should have_content group.name
+ end
end
protected
diff --git a/features/steps/admin/admin_projects.rb b/features/steps/admin/admin_projects.rb
index dd6b4e9810b..b410b23851b 100644
--- a/features/steps/admin/admin_projects.rb
+++ b/features/steps/admin/admin_projects.rb
@@ -16,9 +16,7 @@ class AdminProjects < Spinach::FeatureSteps
Then 'I should see project details' do
project = Project.first
current_path.should == admin_project_path(project)
-
page.should have_content(project.name_with_namespace)
page.should have_content(project.creator.name)
- page.should have_content('Add new team member')
end
end
diff --git a/features/steps/admin/admin_teams.rb b/features/steps/admin/admin_teams.rb
deleted file mode 100644
index 5c66b24bccf..00000000000
--- a/features/steps/admin/admin_teams.rb
+++ /dev/null
@@ -1,234 +0,0 @@
-class AdminTeams < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedActiveTab
- include SharedAdmin
-
- And 'I have own project' do
- create :project
- end
-
- And 'Create gitlab user "John"' do
- @user = create(:user, :name => "John")
- end
-
- And 'I click new team link' do
- click_link "New Team"
- end
-
- And 'submit form with new team info' do
- fill_in 'user_team_name', with: 'gitlab'
- click_button 'Create team'
- end
-
- Then 'I should be redirected to team page' do
- current_path.should == admin_team_path(UserTeam.last)
- end
-
- And 'I should see newly created team' do
- page.should have_content "Team: gitlab"
- end
-
- When 'I visit admin teams page' do
- visit admin_teams_path
- end
-
- When 'I have clean "HardCoders" team' do
- @team = create :user_team, name: "HardCoders", owner: current_user
- end
-
- And 'I visit "HardCoders" team page' do
- visit admin_team_path(UserTeam.find_by_name("HardCoders"))
- end
-
- Then 'I should see only me in members table' do
- members_list = find("#members_list .member")
- members_list.should have_content(current_user.name)
- members_list.should have_content(current_user.email)
- end
-
- When 'I select user "John" from user list as "Developer"' do
- @user ||= User.find_by_name("John")
- within "#team_members" do
- select @user.name, :from => "user_ids"
- select "Developer", :from => "default_project_access"
- end
- end
-
- And 'submit form with new team member info' do
- click_button 'add_members_to_team'
- end
-
- Then 'I should see "John" in teams members list as "Developer"' do
- @user ||= User.find_by_name("John")
- find_in_list("#members_list .member", @user).must_equal true
- end
-
- When 'I visit "John" user admin page' do
- pending 'step not implemented'
- end
-
- Then 'I should see "HardCoders" team in teams table' do
- pending 'step not implemented'
- end
-
- When 'I have "HardCoders" team with "John" member with "Developer" role' do
- @team = create :user_team, name: "HardCoders", owner: current_user
- @user ||= User.find_by_name("John")
- @team.add_member(@user, UserTeam.access_roles["Developer"], group_admin: false)
- end
-
- When 'I have "Shop" project' do
- @project = create :project, name: "Shop"
- end
-
- Then 'I should see empty projects table' do
- page.has_no_css?("#projects_list").must_equal true
- end
-
- When 'I select project "Shop" with max access "Reporter"' do
- @project ||= Project.find_by_name("Shop")
- within "#assign_projects" do
- select @project.name, :from => "project_ids"
- select "Reporter", :from => "greatest_project_access"
- end
-
- end
-
- And 'submit form with new team project info' do
- click_button 'assign_projects_to_team'
- end
-
- Then 'I should see "Shop" project in projects list' do
- project = Project.find_by_name("Shop")
- find_in_list("#projects_list .project", project).must_equal true
- end
-
- When 'I visit "Shop" project admin page' do
- project = Project.find_by_name("Shop")
- visit admin_project_path(project)
- end
-
- And '"HardCoders" team assigned to "Shop" project with "Developer" max role access' do
- @team = UserTeam.find_by_name("HardCoders")
- @project = create :project, name: "Shop"
- @team.assign_to_project(@project, UserTeam.access_roles["Developer"])
- end
-
- When 'I have gitlab user "Jimm"' do
- create :user, name: "Jimm"
- end
-
- Then 'I should see members table without "Jimm" member' do
- user = User.find_by_name("Jimm")
- find_in_list("#members_list .member", user).must_equal false
- end
-
- When 'I select user "Jimm" ub team members list as "Master"' do
- user = User.find_by_name("Jimm")
- within "#team_members" do
- select user.name, :from => "user_ids"
- select "Developer", :from => "default_project_access"
- end
- end
-
- Then 'I should see "Jimm" in teams members list as "Master"' do
- user = User.find_by_name("Jimm")
- find_in_list("#members_list .member", user).must_equal true
- end
-
- Given 'I have users team "HardCoders"' do
- @team = create :user_team, name: "HardCoders"
- end
-
- And 'gitlab user "John" is a member "HardCoders" team' do
- @team = UserTeam.find_by_name("HardCoders")
- @user = User.find_by_name("John")
- @user = create :user, name: "John" unless @user
- @team.add_member(@user, UserTeam.access_roles["Master"], group_admin: false)
- end
-
- And 'gitlab user "Jimm" is a member "HardCoders" team' do
- @team = UserTeam.find_by_name("HardCoders")
- @user = User.find_by_name("Jimm")
- @user = create :user, name: "Jimm" unless @user
- @team.add_member(@user, UserTeam.access_roles["Master"], group_admin: false)
- end
-
- And '"HardCoders" team is assigned to "Shop" project' do
- @team = UserTeam.find_by_name("HardCoders")
- @project = create :project, name: "Shop"
- @team.assign_to_project(@project, UserTeam.access_roles["Developer"])
- end
-
- When 'I visit "HardCoders" team admin page' do
- visit admin_team_path(UserTeam.find_by_name("HardCoders"))
- end
-
- Then 'I shoould see "John" in members list' do
- user = User.find_by_name("John")
- find_in_list("#members_list .member", user).must_equal true
- end
-
- And 'I should see "Jimm" in members list' do
- user = User.find_by_name("Jimm")
- find_in_list("#members_list .member", user).must_equal true
- end
-
- And 'I should see "Shop" in projects list' do
- project = Project.find_by_name("Shop")
- find_in_list("#projects_list .project", project).must_equal true
- end
-
- When 'I click on remove "Jimm" user link' do
- user = User.find_by_name("Jimm")
- click_link "remove_member_#{user.id}"
- end
-
- Then 'I should be redirected to "HardCoders" team admin page' do
- current_path.should == admin_team_path(UserTeam.find_by_name("HardCoders"))
- end
-
- And 'I should not to see "Jimm" user in members list' do
- user = User.find_by_name("Jimm")
- find_in_list("#members_list .member", user).must_equal false
- end
-
- When 'I click on "Relegate" link on "Shop" project' do
- project = Project.find_by_name("Shop")
- click_link "relegate_project_#{project.id}"
- end
-
- Then 'I should see projects liston team page without "Shop" project' do
- project = Project.find_by_name("Shop")
- find_in_list("#projects_list .project", project).must_equal false
- end
-
- Then 'I should see "John" user with role "Reporter" in team table' do
- user = User.find_by_name("John")
- find_in_list(".team_members", user).must_equal true
- end
-
- When 'I click to "Add members" link' do
- click_link "Add members"
- end
-
- When 'I click to "Add projects" link' do
- click_link "Add projects"
- end
-
- protected
-
- def current_team
- @team ||= Team.first
- end
-
- def find_in_list(selector, item)
- members_list = all(selector)
- entered = false
- members_list.each do |member_item|
- entered = true if member_item.has_content?(item.name)
- end
- entered
- end
-end
diff --git a/features/steps/admin/admin_users.rb b/features/steps/admin/admin_users.rb
index 1828ae705ce..33c1344eaeb 100644
--- a/features/steps/admin/admin_users.rb
+++ b/features/steps/admin/admin_users.rb
@@ -8,4 +8,27 @@ class AdminUsers < Spinach::FeatureSteps
page.should have_content user.name
end
end
+
+ And 'Click edit' do
+ @user = User.first
+ find("#edit_user_#{@user.id}").click
+ end
+
+ And 'Input non ascii char in username' do
+ fill_in 'user_username', with: "\u3042\u3044"
+ end
+
+ And 'Click save' do
+ click_button("Save")
+ end
+
+ Then 'See username error message' do
+ within "#error_explanation" do
+ page.should have_content "Username"
+ end
+ end
+
+ And 'Not changed form action url' do
+ page.should have_selector %(form[action="/admin/users/#{@user.username}"])
+ end
end
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index c6832056435..bde32128b92 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -4,7 +4,7 @@ class Dashboard < Spinach::FeatureSteps
include SharedProject
Then 'I should see "New Project" link' do
- page.should have_link "New Project"
+ page.should have_link "New project"
end
Then 'I should see "Shop" project link' do
@@ -22,6 +22,7 @@ class Dashboard < Spinach::FeatureSteps
Then 'I see prefilled new Merge Request page' do
current_path.should == new_project_merge_request_path(@project)
+ 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"
@@ -29,7 +30,7 @@ class Dashboard < Spinach::FeatureSteps
Given 'user with name "John Doe" joined project "Shop"' do
user = create(:user, {name: "John Doe"})
- project = Project.find_by_name "Shop"
+ project.team << [user, :master]
Event.create(
project: project,
author_id: user.id,
@@ -38,12 +39,11 @@ class Dashboard < Spinach::FeatureSteps
end
Then 'I should see "John Doe joined project at Shop" event' do
- page.should have_content "John Doe joined project at Shop"
+ page.should have_content "John Doe joined project at #{project.name_with_namespace}"
end
And 'user with name "John Doe" left project "Shop"' do
user = User.find_by_name "John Doe"
- project = Project.find_by_name "Shop"
Event.create(
project: project,
author_id: user.id,
@@ -52,12 +52,12 @@ class Dashboard < Spinach::FeatureSteps
end
Then 'I should see "John Doe left project at Shop" event' do
- page.should have_content "John Doe left project at Shop"
+ page.should have_content "John Doe left project at #{project.name_with_namespace}"
end
And 'I have group with projects' do
@group = create(:group)
- @project = create(:project, group: @group)
+ @project = create(:project, namespace: @group)
@event = create(:closed_issue_event, project: @project)
@project.team << [current_user, :master]
@@ -83,4 +83,8 @@ class Dashboard < Spinach::FeatureSteps
Then 'I should see 1 project at group list' do
page.find('span.last_activity/span').should have_content('1')
end
+
+ def project
+ @project ||= Project.find_by_name "Shop"
+ end
end
diff --git a/features/steps/dashboard/dashboard_active_tab.rb b/features/steps/dashboard/dashboard_active_tab.rb
index 41ecc48c0d3..8f5f0eed816 100644
--- a/features/steps/dashboard/dashboard_active_tab.rb
+++ b/features/steps/dashboard/dashboard_active_tab.rb
@@ -15,10 +15,6 @@ class DashboardActiveTab < Spinach::FeatureSteps
ensure_active_main_tab('Merge Requests')
end
- Then 'the active main tab should be Search' do
- ensure_active_main_tab('Search')
- end
-
Then 'the active main tab should be Help' do
ensure_active_main_tab('Help')
end
diff --git a/features/steps/dashboard/dashboard_event_filters.rb b/features/steps/dashboard/dashboard_event_filters.rb
index afa15c31332..d0fe5c9b64b 100644
--- a/features/steps/dashboard/dashboard_event_filters.rb
+++ b/features/steps/dashboard/dashboard_event_filters.rb
@@ -1,12 +1,12 @@
class EventFilters < Spinach::FeatureSteps
include SharedAuthentication
- include SharedPaths
+ include SharedPaths
include SharedProject
Then 'I should see push event' do
page.should have_selector('span.pushed')
end
-
+
Then 'I should not see push event' do
page.should_not have_selector('span.pushed')
end
@@ -20,11 +20,11 @@ class EventFilters < Spinach::FeatureSteps
end
Then 'I should see merge request event' do
- page.should have_selector('span.merged')
+ page.should have_selector('span.accepted')
end
And 'I should not see merge request event' do
- page.should_not have_selector('span.merged')
+ page.should_not have_selector('span.accepted')
end
And 'this project has push event' do
@@ -61,14 +61,14 @@ class EventFilters < Spinach::FeatureSteps
end
And 'this project has merge request event' do
- merge_request = create :merge_request, author: @user, project: @project
+ merge_request = create :merge_request, author: @user, source_project: @project, target_project: @project
Event.create(
project: @project,
action: Event::MERGED,
target_id: merge_request.id,
target_type: "MergeRequest",
author_id: @user.id
- )
+ )
end
When 'I click "push" event filter' do
diff --git a/features/steps/dashboard/dashboard_merge_requests.rb b/features/steps/dashboard/dashboard_merge_requests.rb
index 7cfa8a13ff8..6c1fa39f081 100644
--- a/features/steps/dashboard/dashboard_merge_requests.rb
+++ b/features/steps/dashboard/dashboard_merge_requests.rb
@@ -6,18 +6,24 @@ class DashboardMergeRequests < Spinach::FeatureSteps
merge_requests = @user.merge_requests
merge_requests.each do |mr|
page.should have_content(mr.title[0..10])
- page.should have_content(mr.project.name)
+ page.should have_content(mr.target_project.name)
+ page.should have_content(mr.source_project.name)
end
end
And 'I have authored merge requests' do
- project1 = create :project
- project2 = create :project
+ project1_source = create :project
+ project1_target= create :project
+ project2_source = create :project
+ project2_target = create :project
- project1.team << [@user, :master]
- project2.team << [@user, :master]
- merge_request1 = create :merge_request, author: @user, project: project1
- merge_request2 = create :merge_request, author: @user, project: project2
+ project1_source.team << [@user, :master]
+ project1_target.team << [@user, :master]
+ project2_source.team << [@user, :master]
+ project2_target.team << [@user, :master]
+
+ merge_request1 = create :merge_request, author: @user, source_project: project1_source, target_project: project1_target
+ merge_request2 = create :merge_request, author: @user, source_project: project2_source, target_project: project2_target
end
end
diff --git a/features/steps/dashboard/dashboard_projects.rb b/features/steps/dashboard/dashboard_projects.rb
new file mode 100644
index 00000000000..85251565446
--- /dev/null
+++ b/features/steps/dashboard/dashboard_projects.rb
@@ -0,0 +1,11 @@
+class DashboardProjects < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+
+ Then 'I should see projects list' do
+ @user.authorized_projects.all.each do |project|
+ page.should have_link project.name_with_namespace
+ end
+ end
+end
diff --git a/features/steps/dashboard/dashboard_search.rb b/features/steps/dashboard/dashboard_search.rb
index 9c8c879479d..32966a8617a 100644
--- a/features/steps/dashboard/dashboard_search.rb
+++ b/features/steps/dashboard/dashboard_search.rb
@@ -16,15 +16,4 @@ class DashboardSearch < Spinach::FeatureSteps
fill_in "dashboard_search", with: "Contibuting"
click_button "Search"
end
-
- And 'Project "Shop" has wiki page "Contibuting guide"' do
- @wiki_page = create :wiki,
- project: @project,
- title: "Contibuting guide",
- slug: "contributing"
- end
-
- Then 'I should see "Contibuting guide" wiki link' do
- page.should have_link "Contibuting guide"
- end
end
diff --git a/features/steps/group/group.rb b/features/steps/group/group.rb
index 5cfa4756ac3..99ec77a7613 100644
--- a/features/steps/group/group.rb
+++ b/features/steps/group/group.rb
@@ -1,6 +1,7 @@
class Groups < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
+ include Select2Helper
Then 'I should see projects list' do
current_user.authorized_projects.each do |project|
@@ -9,8 +10,9 @@ class Groups < Spinach::FeatureSteps
end
And 'I have group with projects' do
- @group = create(:group, owner: current_user)
- @project = create(:project, group: @group)
+ @group = create(:group)
+ @group.add_owner(current_user)
+ @project = create(:project, namespace: @group)
@event = create(:closed_issue_event, project: @project)
@project.team << [current_user, :master]
@@ -28,7 +30,7 @@ class Groups < Spinach::FeatureSteps
Then 'I should see merge requests from this group assigned to me' do
assigned_to_me(:merge_requests).each do |issue|
- page.should have_content issue.title
+ page.should have_content issue.title[0..80]
end
end
@@ -38,11 +40,11 @@ class Groups < Spinach::FeatureSteps
And 'I select user "John" from list with role "Reporter"' do
user = User.find_by_name("John")
- within "#new_team_member" do
- select user.name, :from => "user_ids"
- select "Reporter", :from => "project_access"
+ within ".new_users_group" do
+ select2(user.id, from: "#user_ids", multiple: true)
+ select "Reporter", from: "group_access"
end
- click_button "Add"
+ click_button "Add users into group"
end
Then 'I should see user "John" in team list' do
@@ -59,22 +61,25 @@ class Groups < Spinach::FeatureSteps
Given 'project from group has merge requests assigned to me' do
create :merge_request,
- project: project,
+ source_project: project,
+ target_project: project,
assignee: current_user,
author: current_user
end
When 'I click new group link' do
- click_link "New Group"
+ click_link "New group"
end
And 'submit form with new group info' do
- fill_in 'group_name', :with => 'Samurai'
+ fill_in 'group_name', with: 'Samurai'
+ fill_in 'group_description', with: 'Tokugawa Shogunate'
click_button "Create group"
end
Then 'I should see newly created group' do
page.should have_content "Samurai"
+ page.should have_content "Tokugawa Shogunate"
page.should have_content "You will only see events from projects in this group"
end
@@ -83,7 +88,7 @@ class Groups < Spinach::FeatureSteps
end
And 'I change group name' do
- fill_in 'group_name', :with => 'new-name'
+ fill_in 'group_name', with: 'new-name'
click_button "Save group"
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index b6833f2bde2..5b2a6321265 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -2,78 +2,141 @@ class Profile < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
- Then 'I should see my profile info' do
+ step 'I should see my profile info' do
page.should have_content "Profile"
page.should have_content @user.name
page.should have_content @user.email
end
- Then 'I change my contact info' do
- fill_in "user_skype", :with => "testskype"
- fill_in "user_linkedin", :with => "testlinkedin"
- fill_in "user_twitter", :with => "testtwitter"
- click_button "Save"
+ step 'I change my contact info' do
+ fill_in "user_skype", with: "testskype"
+ fill_in "user_linkedin", with: "testlinkedin"
+ fill_in "user_twitter", with: "testtwitter"
+ click_button "Save changes"
@user.reload
end
- And 'I should see new contact info' do
+ step 'I should see new contact info' do
@user.skype.should == 'testskype'
@user.linkedin.should == 'testlinkedin'
@user.twitter.should == 'testtwitter'
end
- Then 'I change my password' do
- fill_in "user_password", :with => "222333"
- fill_in "user_password_confirmation", :with => "222333"
- click_button "Save"
+ step 'I try change my password w/o old one' do
+ within '.update-password' do
+ fill_in "user_password", with: "222333"
+ fill_in "user_password_confirmation", with: "222333"
+ click_button "Save"
+ end
end
- When 'I unsuccessfully change my password' do
- fill_in "user_password", with: "password"
- fill_in "user_password_confirmation", with: "confirmation"
- click_button "Save"
+ step 'I change my password' do
+ within '.update-password' do
+ fill_in "user_current_password", with: "123456"
+ fill_in "user_password", with: "222333"
+ fill_in "user_password_confirmation", with: "222333"
+ click_button "Save"
+ end
end
- Then "I should see a password error message" do
+ step 'I unsuccessfully change my password' do
+ within '.update-password' do
+ fill_in "user_current_password", with: "123456"
+ fill_in "user_password", with: "password"
+ fill_in "user_password_confirmation", with: "confirmation"
+ click_button "Save"
+ end
+ end
+
+ step "I should see a missing password error message" do
+ page.should have_content "You must provide a valid current password"
+ end
+
+ step "I should see a password error message" do
page.should have_content "Password doesn't match confirmation"
end
- And 'I should be redirected to sign in page' do
+ step 'I should be redirected to sign in page' do
current_path.should == new_user_session_path
end
- Then 'I reset my token' do
- @old_token = @user.private_token
- click_button "Reset"
+ step 'I reset my token' do
+ within '.update-token' do
+ @old_token = @user.private_token
+ click_button "Reset"
+ end
end
- And 'I should see new token' do
+ step 'I should see new token' do
find("#token").value.should_not == @old_token
find("#token").value.should == @user.reload.private_token
end
- Given 'I have activity' do
+ step 'I have activity' do
create(:closed_issue_event, author: current_user)
end
- Then 'I should see my activity' do
+ step 'I should see my activity' do
page.should have_content "#{current_user.name} closed issue"
end
- When "I change my application theme" do
- choose "Violet"
+ step "I change my application theme" do
+ within '.application-theme' do
+ choose "Violet"
+ end
end
- When "I change my code preview theme" do
- choose "Dark code preview"
+ step "I change my code preview theme" do
+ within '.code-preview-theme' do
+ choose "Solarized dark"
+ end
end
- Then "I should see the theme change immediately" do
+ step "I should see the theme change immediately" do
page.should have_selector('body.ui_color')
page.should_not have_selector('body.ui_basic')
end
- Then "I should receive feedback that the changes were saved" do
- page.should have_content("Saved")
+ step "I should receive feedback that the changes were saved" do
+ page.should have_content("saved")
+ end
+
+ step 'my password is expired' do
+ current_user.update_attributes(password_expires_at: Time.now - 1.hour)
+ end
+
+ step "I am not an ldap user" do
+ current_user.update_attributes(extern_uid: nil, provider: '')
+ current_user.ldap_user?.should be_false
+ end
+
+ step 'I redirected to expired password page' do
+ current_path.should == new_profile_password_path
+ end
+
+ step 'I submit new password' do
+ fill_in :user_password, with: '12345678'
+ fill_in :user_password_confirmation, with: '12345678'
+ click_button "Set new password"
+ end
+
+ step 'I redirected to sign in page' do
+ current_path.should == new_user_session_path
+ end
+
+ step 'I should be redirected to account page' do
+ current_path.should == account_profile_path
+ end
+
+ step 'I click on my profile picture' do
+ click_link 'profile-pic'
+ end
+
+ step 'I should see my user page' do
+ page.should have_content "User Activity"
+
+ within '.navbar-gitlab' do
+ page.should have_content current_user.name
+ end
end
end
diff --git a/features/steps/profile/profile_notifications.rb b/features/steps/profile/profile_notifications.rb
new file mode 100644
index 00000000000..7a41687dfde
--- /dev/null
+++ b/features/steps/profile/profile_notifications.rb
@@ -0,0 +1,13 @@
+class ProfileNotifications < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+
+ step 'I visit profile notifications page' do
+ visit profile_notifications_path
+ end
+
+ 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/profile_ssh_keys.rb
index 8ae1fa91025..65bfc505d85 100644
--- a/features/steps/profile/profile_ssh_keys.rb
+++ b/features/steps/profile/profile_ssh_keys.rb
@@ -8,20 +8,20 @@ class ProfileSshKeys < Spinach::FeatureSteps
end
Given 'I click link "Add new"' do
- click_link "Add new"
+ click_link "Add SSH Key"
end
And 'I submit new ssh key "Laptop"' do
- fill_in "key_title", :with => "Laptop"
- fill_in "key_key", :with => "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
- click_button "Save"
+ fill_in "key_title", with: "Laptop"
+ fill_in "key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
+ click_button "Add key"
end
Then 'I should see new ssh key "Laptop"' do
key = Key.find_by_title("Laptop")
page.should have_content(key.title)
page.should have_content(key.key)
- current_path.should == key_path(key)
+ current_path.should == profile_key_path(key)
end
Given 'I click link "Work"' do
@@ -33,7 +33,7 @@ class ProfileSshKeys < Spinach::FeatureSteps
end
Then 'I visit profile keys page' do
- visit keys_path
+ visit profile_keys_path
end
And 'I should not see "Work" ssh key' do
@@ -43,6 +43,6 @@ class ProfileSshKeys < Spinach::FeatureSteps
end
And 'I have ssh key "ssh-rsa Work"' do
- create(:key, :user => @user, :title => "ssh-rsa Work", :key => "jfKLJDFKSFJSHFJssh-rsa Work")
+ create(:key, user: @user, title: "ssh-rsa Work", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+L3TbFegm3k8QjejSwemk4HhlRh+DuN679Pc5ckqE/MPhVtE/+kZQDYCTB284GiT2aIoGzmZ8ee9TkaoejAsBwlA+Wz2Q3vhz65X6sMgalRwpdJx8kSEUYV8ZPV3MZvPo8KdNg993o4jL6G36GDW4BPIyO6FPZhfsawdf6liVD0Xo5kibIK7B9VoE178cdLQtLpS2YolRwf5yy6XR6hbbBGQR+6xrGOdP16eGZDb1CE2bMvvJijjloFqPscGktWOqW+nfh5txwFfBzlfARDTBsS8WZtg3Yoj1kn33kPsWRlgHfNutFRAIynDuDdQzQq8tTtVwm+Yi75RfcPHW8y3P Work")
end
end
diff --git a/features/steps/project/create_project.rb b/features/steps/project/create_project.rb
index 0d9727732c7..b59345e7078 100644
--- a/features/steps/project/create_project.rb
+++ b/features/steps/project/create_project.rb
@@ -17,4 +17,26 @@ class CreateProject < Spinach::FeatureSteps
page.should have_content "git remote"
page.should have_content Project.last.url_to_repo
end
+
+ Then 'I see empty project instuctions' do
+ page.should have_content "git init"
+ page.should have_content "git remote"
+ page.should have_content Project.last.url_to_repo
+ end
+
+ And 'I click on HTTP' do
+ click_button 'HTTP'
+ end
+
+ Then 'Remote url should update to http link' do
+ page.should have_content "git remote add origin #{Project.last.http_url_to_repo}"
+ end
+
+ And 'If I click on SSH' do
+ click_button 'SSH'
+ end
+
+ Then 'Remote url should update to ssh link' do
+ page.should have_content "git remote add origin #{Project.last.url_to_repo}"
+ end
end
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
new file mode 100644
index 00000000000..7f7492bfd6d
--- /dev/null
+++ b/features/steps/project/deploy_keys.rb
@@ -0,0 +1,53 @@
+class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ step 'project has deploy key' do
+ create(:deploy_keys_project, project: @project)
+ end
+
+ step 'I should see project deploy keys' do
+ within '.enabled-keys' do
+ page.should have_content deploy_key.title
+ end
+ end
+
+ step 'I click \'New Deploy Key\'' do
+ click_link 'New Deploy Key'
+ end
+
+ step 'I submit new deploy key' do
+ fill_in "deploy_key_title", with: "laptop"
+ fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
+ click_button "Create"
+ end
+
+ step 'I should be on deploy keys page' do
+ current_path.should == project_deploy_keys_path(@project)
+ end
+
+ step 'I should see newly created deploy key' do
+ within '.enabled-keys' do
+ page.should have_content(deploy_key.title)
+ end
+ end
+
+ step 'other project has deploy key' do
+ @second_project = create :project, namespace: current_user.namespace
+ @second_project.team << [current_user, :master]
+ create(:deploy_keys_project, project: @second_project)
+ end
+
+ step 'I click attach deploy key' do
+ within '.available-keys' do
+ click_link 'Enable'
+ end
+ end
+
+ protected
+
+ def deploy_key
+ @project.deploy_keys.last
+ end
+end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index e9ef1495dd1..a96b086fae5 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -9,7 +9,7 @@ class ProjectFeature < Spinach::FeatureSteps
end
And 'I save project' do
- click_button 'Save'
+ click_button 'Save changes'
end
Then 'I should see project with new settings' do
diff --git a/features/steps/project/project_active_tab.rb b/features/steps/project/project_active_tab.rb
index bce67c82962..e04a17168be 100644
--- a/features/steps/project/project_active_tab.rb
+++ b/features/steps/project/project_active_tab.rb
@@ -10,6 +10,10 @@ class ProjectActiveTab < Spinach::FeatureSteps
ensure_active_main_tab('Home')
end
+ Then 'the active main tab should be Settings' do
+ ensure_active_main_tab('Settings')
+ end
+
Then 'the active main tab should be Files' do
ensure_active_main_tab('Files')
end
@@ -41,7 +45,7 @@ class ProjectActiveTab < Spinach::FeatureSteps
# Sub Tabs: Home
Given 'I click the "Team" tab' do
- click_link('Team')
+ click_link('Members')
end
Given 'I click the "Attachments" tab' do
@@ -57,39 +61,27 @@ class ProjectActiveTab < Spinach::FeatureSteps
end
Given 'I click the "Hooks" tab' do
- click_link('Hooks')
+ click_link('Web Hooks')
end
Given 'I click the "Deploy Keys" tab' do
click_link('Deploy Keys')
end
- Then 'the active sub tab should be Show' do
- ensure_active_sub_tab('Show')
- end
-
- Then 'the active sub tab should be Team' do
- ensure_active_sub_tab('Team')
- end
-
- Then 'the active sub tab should be Attachments' do
- ensure_active_sub_tab('Attachments')
- end
-
- Then 'the active sub tab should be Snippets' do
- ensure_active_sub_tab('Snippets')
+ Then 'the active sub nav should be Team' do
+ ensure_active_sub_nav('Members')
end
- Then 'the active sub tab should be Edit' do
- ensure_active_sub_tab('Edit')
+ Then 'the active sub nav should be Edit' do
+ ensure_active_sub_nav('Edit Project')
end
- Then 'the active sub tab should be Hooks' do
- ensure_active_sub_tab('Hooks')
+ Then 'the active sub nav should be Hooks' do
+ ensure_active_sub_nav('Web Hooks')
end
- Then 'the active sub tab should be Deploy Keys' do
- ensure_active_sub_tab('Deploy Keys')
+ Then 'the active sub nav should be Deploy Keys' do
+ ensure_active_sub_nav('Deploy Keys')
end
# Sub Tabs: Commits
diff --git a/features/steps/project/project_browse_branches.rb b/features/steps/project/project_browse_branches.rb
index 2f6e185deea..e77825411f3 100644
--- a/features/steps/project/project_browse_branches.rb
+++ b/features/steps/project/project_browse_branches.rb
@@ -22,7 +22,7 @@ class ProjectBrowseBranches < Spinach::FeatureSteps
end
Then 'I should see "Shop" protected branches list' do
- within "table" do
+ within ".protected-branches-list" do
page.should have_content "stable"
page.should_not have_content "master"
end
@@ -30,6 +30,6 @@ class ProjectBrowseBranches < Spinach::FeatureSteps
And 'project "Shop" has protected branches' do
project = Project.find_by_name("Shop")
- project.protected_branches.create(:name => "stable")
+ project.protected_branches.create(name: "stable")
end
end
diff --git a/features/steps/project/project_browse_commits.rb b/features/steps/project/project_browse_commits.rb
index 3433c2ba5f6..650bc3a16f7 100644
--- a/features/steps/project/project_browse_commits.rb
+++ b/features/steps/project/project_browse_commits.rb
@@ -15,11 +15,11 @@ class ProjectBrowseCommits < Spinach::FeatureSteps
end
Then 'I see commits atom feed' do
- commit = CommitDecorator.decorate(@project.repository.commit)
+ commit = @project.repository.commit
page.response_headers['Content-Type'].should have_content("application/atom+xml")
- page.body.should have_selector("title", :text => "Recent commits to #{@project.name}")
- page.body.should have_selector("author email", :text => commit.author_email)
- page.body.should have_selector("entry summary", :text => commit.description)
+ page.body.should have_selector("title", text: "Recent commits to #{@project.name}")
+ page.body.should have_selector("author email", text: commit.author_email)
+ page.body.should have_selector("entry summary", text: commit.description)
end
Given 'I click on commit link' do
@@ -48,14 +48,44 @@ class ProjectBrowseCommits < Spinach::FeatureSteps
page.should have_selector('ul.breadcrumb span.divider', count: 3)
page.should have_selector('ul.breadcrumb a', count: 4)
- find('ul.breadcrumb li:first a')['href'].should match(/#{@project.path_with_namespace}\/commits\/master\z/)
+ find('ul.breadcrumb li:nth-child(2) a')['href'].should match(/#{@project.path_with_namespace}\/commits\/master\z/)
find('ul.breadcrumb li:last a')['href'].should match(%r{master/app/models/project\.rb\z})
end
Then 'I see commits stats' do
- page.should have_content 'Stats'
+ page.should have_content 'Top 50 Committers'
page.should have_content 'Committers'
page.should have_content 'Total commits'
page.should have_content 'Authors'
end
+
+ Given 'I visit big commit page' do
+ visit project_commit_path(@project, BigCommits::BIG_COMMIT_ID)
+ end
+
+ Then 'I see big commit warning' do
+ page.should have_content BigCommits::BIG_COMMIT_MESSAGE
+ page.should have_content "Warning! This is a large diff"
+ page.should have_content "If you still want to see the diff"
+ end
+
+ Given 'I visit huge commit page' do
+ visit project_commit_path(@project, BigCommits::HUGE_COMMIT_ID)
+ end
+
+ Then 'I see huge commit message' do
+ page.should have_content BigCommits::HUGE_COMMIT_MESSAGE
+ page.should have_content "Warning! This is a large diff"
+ page.should_not have_content "If you still want to see the diff"
+ end
+
+ Given 'I visit a commit with an image that changed' do
+ visit project_commit_path(@project, 'cc1ba255d6c5ffdce87a357ba7ccc397a4f4026b')
+ end
+
+ Then 'The diff links to both the previous and current image' do
+ links = page.all('.two-up span div a')
+ links[0]['href'].should =~ %r{blob/bc3735004cb45cec5e0e4fa92710897a910a5957}
+ links[1]['href'].should =~ %r{blob/cc1ba255d6c5ffdce87a357ba7ccc397a4f4026b}
+ end
end
diff --git a/features/steps/project/project_browse_files.rb b/features/steps/project/project_browse_files.rb
index 4efce0dcffc..71360fb6bd5 100644
--- a/features/steps/project/project_browse_files.rb
+++ b/features/steps/project/project_browse_files.rb
@@ -16,12 +16,12 @@ class ProjectBrowseFiles < Spinach::FeatureSteps
page.should have_content "Gemfile"
end
- Given 'I click on "Gemfile" file in repo' do
- click_link "Gemfile"
+ Given 'I click on "Gemfile.lock" file in repo' do
+ click_link "Gemfile.lock"
end
Then 'I should see it content' do
- page.should have_content "rubygems.org"
+ page.should have_content "DEPENDENCIES"
end
And 'I click link "raw"' do
diff --git a/features/steps/project/project_browse_git_repo.rb b/features/steps/project/project_browse_git_repo.rb
index 19edfb076eb..cd9a60f49cb 100644
--- a/features/steps/project/project_browse_git_repo.rb
+++ b/features/steps/project/project_browse_git_repo.rb
@@ -3,8 +3,8 @@ class ProjectBrowseGitRepo < Spinach::FeatureSteps
include SharedProject
include SharedPaths
- Given 'I click on "Gemfile" file in repo' do
- click_link "Gemfile"
+ Given 'I click on "Gemfile.lock" file in repo' do
+ click_link "Gemfile.lock"
end
And 'I click blame button' do
@@ -12,7 +12,7 @@ class ProjectBrowseGitRepo < Spinach::FeatureSteps
end
Then 'I should see git file blame' do
- page.should have_content "rubygems.org"
+ page.should have_content "DEPENDENCIES"
page.should have_content "Dmitriy Zaporozhets"
page.should have_content "Moving to rails 3.2"
end
diff --git a/features/steps/project/project_fork.rb b/features/steps/project/project_fork.rb
new file mode 100644
index 00000000000..858c7d11b32
--- /dev/null
+++ b/features/steps/project/project_fork.rb
@@ -0,0 +1,36 @@
+class ForkProject < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+
+ step 'I click link "Fork"' do
+ page.should have_content "Shop"
+ page.should have_content "Fork"
+ Gitlab::Shell.any_instance.stub(:fork_repository).and_return(true)
+ click_link "Fork"
+ end
+
+ step 'I am a member of project "Shop"' do
+ @project = Project.find_by_name "Shop"
+ @project ||= create(:project_with_code, name: "Shop", group: create(:group))
+ @project.team << [@user, :reporter]
+ end
+
+ step 'I should see the forked project page' do
+ page.should have_content "Project was successfully forked."
+ current_path.should include current_user.namespace.path
+ @forked_project = Project.find_by_namespace_id(current_user.namespace.path)
+ end
+
+ step 'I already have a project named "Shop" in my namespace' do
+ current_user.namespace ||= create(:namespace)
+ current_user.namespace.should_not be_nil
+ current_user.namespace.path.should_not be_nil
+ @my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace)
+ end
+
+ step 'I should see a "Name has already been taken" warning' do
+ page.should have_content "Name has already been taken"
+ end
+
+end
diff --git a/features/steps/project/project_forked_merge_requests.rb b/features/steps/project/project_forked_merge_requests.rb
new file mode 100644
index 00000000000..f7bf085a423
--- /dev/null
+++ b/features/steps/project/project_forked_merge_requests.rb
@@ -0,0 +1,183 @@
+class ProjectForkedMergeRequests < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedNote
+ include SharedPaths
+ include ChosenHelper
+
+ step 'I am a member of project "Shop"' do
+ @project = Project.find_by_name "Shop"
+ @project ||= create(:project_with_code, name: "Shop")
+ @project.team << [@user, :reporter]
+ end
+
+ step 'I have a project forked off of "Shop" called "Forked Shop"' do
+ @forking_user = @user
+ forked_project_link = build(:forked_project_link)
+ @forked_project = Project.find_by_name "Forked Shop"
+ @forked_project ||= create(:source_project_with_code, name: "Forked Shop", forked_project_link: forked_project_link, creator_id: @forking_user.id , namespace: @forking_user.namespace)
+
+ forked_project_link.forked_from_project = @project
+ forked_project_link.forked_to_project = @forked_project
+ @forked_project.team << [@forking_user , :master]
+ forked_project_link.save!
+ end
+
+ step 'I click link "New Merge Request"' do
+ click_link "New Merge Request"
+ end
+
+ step 'I should see merge request "Merge Request On Forked Project"' do
+ @project.merge_requests.size.should >= 1
+ @merge_request = @project.merge_requests.last
+ current_path.should == project_merge_request_path(@project, @merge_request)
+ @merge_request.title.should == "Merge Request On Forked Project"
+ @merge_request.source_project.should == @forked_project
+ @merge_request.source_branch.should == "master"
+ @merge_request.target_branch.should == "stable"
+ page.should have_content @forked_project.path_with_namespace
+ page.should have_content @project.path_with_namespace
+ page.should have_content @merge_request.source_branch
+ page.should have_content @merge_request.target_branch
+ end
+
+ step 'I fill out a "Merge Request On Forked Project" merge request' do
+ chosen @forked_project.id, from: "#merge_request_source_project_id"
+ chosen @project.id, from: "#merge_request_target_project_id"
+
+ find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
+ find(:select, "merge_request_target_project_id", {}).value.should == @project.id.to_s
+
+ chosen "master", from: "#merge_request_source_branch"
+ chosen "stable", from: "#merge_request_target_branch"
+
+ find(:select, "merge_request_source_branch", {}).value.should == 'master'
+ find(:select, "merge_request_target_branch", {}).value.should == 'stable'
+
+ fill_in "merge_request_title", with: "Merge Request On Forked Project"
+ end
+
+ step 'I submit the merge request' do
+ click_button "Submit merge request"
+ end
+
+ step 'I follow the target commit link' do
+ commit = @project.repository.commit
+ click_link commit.short_id(8)
+ end
+
+ step 'I should see the commit under the forked from project' do
+ commit = @project.repository.commit
+ page.should have_content(commit.message)
+ end
+
+ step 'I click "Create Merge Request on fork" link' do
+ click_link "Create Merge Request on fork"
+ end
+
+ step 'I see prefilled new Merge Request page for the forked project' do
+ current_path.should == new_project_merge_request_path(@forked_project)
+ find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
+ find("#merge_request_target_project_id").value.should == @project.id.to_s
+ find("#merge_request_source_branch").value.should have_content "new_design"
+ find("#merge_request_target_branch").value.should have_content "master"
+ find("#merge_request_title").value.should == "New Design"
+ verify_commit_link(".mr_target_commit",@project)
+ verify_commit_link(".mr_source_commit",@forked_project)
+ end
+
+ step 'I update the merge request title' do
+ fill_in "merge_request_title", with: "An Edited Forked Merge Request"
+ end
+
+ step 'I save the merge request' do
+ click_button "Save changes"
+ end
+
+ step 'I should see the edited merge request' do
+ page.should have_content "An Edited Forked Merge Request"
+ @project.merge_requests.size.should >= 1
+ @merge_request = @project.merge_requests.last
+ current_path.should == project_merge_request_path(@project, @merge_request)
+ @merge_request.source_project.should == @forked_project
+ @merge_request.source_branch.should == "master"
+ @merge_request.target_branch.should == "stable"
+ page.should have_content @forked_project.path_with_namespace
+ page.should have_content @project.path_with_namespace
+ page.should have_content @merge_request.source_branch
+ page.should have_content @merge_request.target_branch
+ end
+
+ step 'I should see last push widget' do
+ page.should have_content "You pushed to new_design"
+ page.should have_link "Create Merge Request"
+ end
+
+ step 'project "Forked Shop" has push event' do
+ @forked_project = Project.find_by_name("Forked Shop")
+
+ data = {
+ before: "0000000000000000000000000000000000000000",
+ after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
+ ref: "refs/heads/new_design",
+ user_id: @user.id,
+ user_name: @user.name,
+ repository: {
+ name: @forked_project.name,
+ url: "localhost/rubinius",
+ description: "",
+ homepage: "localhost/rubinius",
+ private: true
+ }
+ }
+
+ @event = Event.create(
+ project: @forked_project,
+ action: Event::PUSHED,
+ data: data,
+ author_id: @user.id
+ )
+ end
+
+
+ step 'I click link edit "Merge Request On Forked Project"' do
+ find("#edit_merge_request").click
+ end
+
+ step 'I see the edit page prefilled for "Merge Request On Forked Project"' do
+ current_path.should == edit_project_merge_request_path(@project, @merge_request)
+ page.should have_content "Edit merge request ##{@merge_request.id}"
+ find("#merge_request_title").value.should == "Merge Request On Forked Project"
+ find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
+ find("#merge_request_target_project_id").value.should == @project.id.to_s
+ find("#merge_request_source_branch").value.should have_content "master"
+ verify_commit_link(".mr_source_commit",@forked_project)
+ find("#merge_request_target_branch").value.should have_content "stable"
+ verify_commit_link(".mr_target_commit",@project)
+ end
+
+ 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"
+ 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_source_branch", {}).value.should == ""
+ find(:select, "merge_request_target_branch", {}).value.should == ""
+ end
+
+ 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
+
+ def project
+ @project ||= Project.find_by_name!("Shop")
+ end
+
+ # Verify a link is generated against the correct project
+ def verify_commit_link(container_div, container_project)
+ # This should force a wait for the javascript to execute
+ find(:div,container_div).find(".commit_short_id")['href'].should have_content "#{container_project.path_with_namespace}/commit"
+ end
+end
diff --git a/features/steps/project/project_graph.rb b/features/steps/project/project_graph.rb
new file mode 100644
index 00000000000..50942b3cbb3
--- /dev/null
+++ b/features/steps/project/project_graph.rb
@@ -0,0 +1,13 @@
+class ProjectGraph < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+
+ Then 'page should have graphs' do
+ page.should have_selector ".stat-graph"
+ end
+
+ When 'I visit project "Shop" graph page' do
+ project = Project.find_by_name("Shop")
+ visit project_graph_path(project, "master")
+ end
+end
diff --git a/features/steps/project/project_issues.rb b/features/steps/project/project_issues.rb
index 2103aeb1715..801fff78a52 100644
--- a/features/steps/project/project_issues.rb
+++ b/features/steps/project/project_issues.rb
@@ -12,6 +12,10 @@ class ProjectIssues < Spinach::FeatureSteps
page.should_not have_content "Release 0.3"
end
+ And 'I should not see "Tweet control" in issues' do
+ page.should_not have_content "Tweet control"
+ end
+
Given 'I click link "Closed"' do
click_link "Closed"
end
@@ -41,7 +45,7 @@ class ProjectIssues < Spinach::FeatureSteps
end
And 'I submit new issue "500 error on profile"' do
- fill_in "issue_title", :with => "500 error on profile"
+ fill_in "issue_title", with: "500 error on profile"
click_button "Submit new issue"
end
@@ -56,16 +60,16 @@ class ProjectIssues < Spinach::FeatureSteps
page.should have_content issue.project.name
end
- Given 'I fill in issue search with "Release"' do
- fill_in 'issue_search', with: "Release"
+ Given 'I fill in issue search with "Re"' do
+ fill_in 'issue_search', with: "Re"
end
- Given 'I fill in issue search with "Bug"' do
- fill_in 'issue_search', with: "Bug"
+ Given 'I fill in issue search with "Bu"' do
+ fill_in 'issue_search', with: "Bu"
end
- And 'I fill in issue search with "0.3"' do
- fill_in 'issue_search', with: "0.3"
+ And 'I fill in issue search with ".3"' do
+ fill_in 'issue_search', with: ".3"
end
And 'I fill in issue search with "Something"' do
@@ -78,16 +82,16 @@ class ProjectIssues < Spinach::FeatureSteps
Given 'project "Shop" has milestone "v2.2"' do
project = Project.find_by_name("Shop")
- milestone = create(:milestone, :title => "v2.2", :project => project)
+ milestone = create(:milestone, title: "v2.2", project: project)
- 3.times { create(:issue, :project => project, :milestone => milestone) }
+ 3.times { create(:issue, project: project, milestone: milestone) }
end
And 'project "Shop" has milestone "v3.0"' do
project = Project.find_by_name("Shop")
- milestone = create(:milestone, :title => "v3.0", :project => project)
+ milestone = create(:milestone, title: "v3.0", project: project)
- 3.times { create(:issue, :project => project, :milestone => milestone) }
+ 3.times { create(:issue, project: project, milestone: milestone) }
end
When 'I select milestone "v3.0"' do
@@ -115,17 +119,24 @@ class ProjectIssues < Spinach::FeatureSteps
And 'project "Shop" have "Release 0.4" open issue' do
project = Project.find_by_name("Shop")
create(:issue,
- :title => "Release 0.4",
- :project => project,
- :author => project.users.first)
+ title: "Release 0.4",
+ project: project,
+ author: project.users.first)
end
- And 'project "Shop" have "Release 0.3" closed issue' do
+ And 'project "Shop" have "Tweet control" open issue' do
project = Project.find_by_name("Shop")
create(:issue,
- :title => "Release 0.3",
- :project => project,
- :author => project.users.first,
- :closed => true)
+ title: "Tweet control",
+ project: project,
+ author: project.users.first)
+ end
+
+ And 'project "Shop" have "Release 0.3" closed issue' do
+ project = Project.find_by_name("Shop")
+ create(:closed_issue,
+ title: "Release 0.3",
+ project: project,
+ author: project.users.first)
end
end
diff --git a/features/steps/project/project_merge_requests.rb b/features/steps/project/project_merge_requests.rb
index 329261add2a..7c70482deb5 100644
--- a/features/steps/project/project_merge_requests.rb
+++ b/features/steps/project/project_merge_requests.rb
@@ -25,8 +25,8 @@ class ProjectMergeRequests < Spinach::FeatureSteps
end
Then 'I should see closed merge request "Bug NS-04"' do
- mr = MergeRequest.find_by_title("Bug NS-04")
- mr.closed.should be_true
+ merge_request = MergeRequest.find_by_title!("Bug NS-04")
+ merge_request.closed?.should be_true
page.should have_content "Closed by"
end
@@ -56,54 +56,59 @@ class ProjectMergeRequests < Spinach::FeatureSteps
end
And 'I submit new merge request "Wiki Feature"' do
+ #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"
fill_in "merge_request_title", :with => "Wiki Feature"
select "master", :from => "merge_request_source_branch"
- select "stable", :from => "merge_request_target_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
And 'project "Shop" have "Bug NS-04" open merge request' do
- project = Project.find_by_name("Shop")
create(:merge_request,
title: "Bug NS-04",
- project: project,
+ source_project: project,
+ target_project: project,
author: project.users.first)
end
And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do
- project = Project.find_by_name("Shop")
create(:merge_request_with_diffs,
title: "Bug NS-05",
- project: project,
+ source_project: project,
+ target_project: project,
author: project.users.first)
end
And 'project "Shop" have "Feature NS-03" closed merge request' do
- project = Project.find_by_name("Shop")
- create(:merge_request,
+ create(:closed_merge_request,
title: "Feature NS-03",
- project: project,
- author: project.users.first,
- closed: true)
+ source_project: project,
+ target_project: project,
+ author: project.users.first)
end
And 'I switch to the diff tab' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- visit diffs_project_merge_request_path(mr.project, mr)
+ visit diffs_project_merge_request_path(project, merge_request)
end
And 'I switch to the merge request\'s comments tab' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- visit project_merge_request_path(mr.project, mr)
+ visit project_merge_request_path(project, merge_request)
end
And 'I click on the first commit in the merge request' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- click_link mr.commits.first.short_id(8)
+
+ click_link merge_request.commits.first.short_id(8)
end
And 'I leave a comment on the diff page' do
- find("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185 .add-diff-note").click
+ init_diff_note
within('.js-temp-notes-holder') do
fill_in "note_note", with: "One comment to rule them all"
@@ -112,7 +117,7 @@ class ProjectMergeRequests < Spinach::FeatureSteps
end
And 'I leave a comment like "Line is wrong" on line 185 of the first file' do
- find("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185 .add-diff-note").click
+ init_diff_note
within(".js-temp-notes-holder") do
fill_in "note_note", with: "Line is wrong"
@@ -122,31 +127,32 @@ class ProjectMergeRequests < Spinach::FeatureSteps
end
Then 'I should see a discussion has started on line 185' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- first_commit = mr.commits.first
- first_diff = first_commit.diffs.first
page.should have_content "#{current_user.name} started a discussion on this merge request diff"
- page.should have_content "#{first_diff.b_path}:L185"
+ page.should have_content "app/assets/stylesheets/tree.scss:L185"
page.should have_content "Line is wrong"
end
Then 'I should see a discussion has started on commit bcf03b5de6c:L185' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- first_commit = mr.commits.first
- first_diff = first_commit.diffs.first
page.should have_content "#{current_user.name} started a discussion on commit"
- page.should have_content first_commit.short_id(8)
- page.should have_content "#{first_diff.b_path}:L185"
+ page.should have_content "app/assets/stylesheets/tree.scss:L185"
page.should have_content "Line is wrong"
end
Then 'I should see a discussion has started on commit bcf03b5de6c' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- first_commit = mr.st_commits.first
- first_diff = first_commit.diffs.first
page.should have_content "#{current_user.name} started a discussion on commit bcf03b5de6c"
- page.should have_content first_commit.short_id(8)
page.should have_content "One comment to rule them all"
- page.should have_content "#{first_diff.b_path}:L185"
+ page.should have_content "app/assets/stylesheets/tree.scss:L185"
+ end
+
+ def project
+ @project ||= Project.find_by_name!("Shop")
+ end
+
+ def merge_request
+ @merge_request ||= MergeRequest.find_by_title!("Bug NS-05")
+ end
+
+ def init_diff_note
+ find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click
end
end
diff --git a/features/steps/project/project_milestones.rb b/features/steps/project/project_milestones.rb
index 1350938ee9a..c4d0d176f3a 100644
--- a/features/steps/project/project_milestones.rb
+++ b/features/steps/project/project_milestones.rb
@@ -19,7 +19,7 @@ class ProjectMilestones < Spinach::FeatureSteps
end
And 'I submit new milestone "v2.3"' do
- fill_in "milestone_title", :with => "v2.3"
+ fill_in "milestone_title", with: "v2.3"
click_button "Create milestone"
end
@@ -32,9 +32,9 @@ class ProjectMilestones < Spinach::FeatureSteps
And 'project "Shop" has milestone "v2.2"' do
project = Project.find_by_name("Shop")
- milestone = create(:milestone, :title => "v2.2", :project => project)
+ milestone = create(:milestone, title: "v2.2", project: project)
- 3.times { create(:issue, :project => project, :milestone => milestone) }
+ 3.times { create(:issue, project: project, milestone: milestone) }
end
Given 'the milestone has open and closed issues' do
@@ -50,12 +50,6 @@ class ProjectMilestones < Spinach::FeatureSteps
end
Then "I should see 3 issues" do
- page.should have_selector('.milestone-issue-filter .well-list li', count: 4)
- page.should have_selector('.milestone-issue-filter .well-list li.hide', count: 1)
- end
-
- Then "I should see 4 issues" do
- page.should have_selector('.milestone-issue-filter .well-list li', count: 4)
- page.should_not have_selector('.milestone-issue-filter .well-list li.hide')
+ page.should have_selector('#tab-issues li', count: 4)
end
end
diff --git a/features/steps/project/project_network_graph.rb b/features/steps/project/project_network_graph.rb
index f26deff9367..127adecf7ed 100644
--- a/features/steps/project/project_network_graph.rb
+++ b/features/steps/project/project_network_graph.rb
@@ -3,17 +3,90 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
include SharedProject
Then 'page should have network graph' do
- page.should have_content "Project Network Graph"
- within ".graph" do
- page.should have_content "master"
- end
+ page.should have_selector ".network-graph"
end
- And 'I visit project "Shop" network page' do
- # Stub Graph::JsonBuilder max_size to speed up test (10 commits vs. 650)
- Gitlab::Graph::JsonBuilder.stub(max_count: 10)
+ When 'I visit project "Shop" network page' do
+ # Stub Graph max_size to speed up test (10 commits vs. 650)
+ Network::Graph.stub(max_count: 10)
project = Project.find_by_name("Shop")
- visit project_graph_path(project, "master")
+ visit project_network_path(project, "master")
+ end
+
+ And 'page should select "master" in select box' do
+ page.should have_selector '.chosen-single span', text: "master"
+ end
+
+ And 'page should select "v2.1.0" in select box' do
+ page.should have_selector '.chosen-single span', text: "v2.1.0"
+ end
+
+ And 'page should have "master" on graph' do
+ within '.network-graph' do
+ page.should have_content 'master'
+ end
+ end
+
+ When 'I switch ref to "stable"' do
+ page.select 'stable', from: 'ref'
+ sleep 2
+ end
+
+ When 'I switch ref to "v2.1.0"' do
+ page.select 'v2.1.0', from: 'ref'
+ sleep 2
+ end
+
+ When 'click "Show only selected branch" checkbox' do
+ find('#filter_ref').click
+ sleep 2
+ end
+
+ Then 'page should have content not cotaining "v2.1.0"' do
+ within '.network-graph' do
+ page.should have_content 'cleaning'
+ end
+ end
+
+ Then 'page should not have content not cotaining "v2.1.0"' do
+ within '.network-graph' do
+ page.should_not have_content 'cleaning'
+ end
+ end
+
+ And 'page should select "stable" in select box' do
+ page.should have_selector '.chosen-single span', text: "stable"
+ end
+
+ And 'page should select "v2.1.0" in select box' do
+ page.should have_selector '.chosen-single span', text: "v2.1.0"
+ end
+
+ And 'page should have "stable" on graph' do
+ within '.network-graph' do
+ page.should have_content 'stable'
+ end
+ end
+
+ When 'I looking for a commit by SHA of "v2.1.0"' do
+ within ".content .search" do
+ fill_in 'extended_sha1', with: '98d6492'
+ find('button').click
+ end
+ sleep 2
+ end
+
+ And 'page should have "v2.1.0" on graph' do
+ within '.network-graph' do
+ page.should have_content 'v2.1.0'
+ end
+ end
+
+ When 'I look for a commit by ";"' do
+ within ".content .search" do
+ fill_in 'extended_sha1', with: ';'
+ find('button').click
+ end
end
end
diff --git a/features/steps/project/project_search_code.rb b/features/steps/project/project_search_code.rb
new file mode 100644
index 00000000000..d117b019a15
--- /dev/null
+++ b/features/steps/project/project_search_code.rb
@@ -0,0 +1,17 @@
+class ProjectSearchCode < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ When 'I search for term "Welcome to GitLab"' do
+ fill_in "search", with: "Welcome to GitLab"
+ click_button "Go"
+ click_link 'Repository Code'
+ end
+
+ Then 'I should see files from repository containing "Welcome to GitLab"' do
+ page.should have_content "Welcome to GitLab"
+ page.should have_content "GitLab is a free project and repository management application"
+ end
+
+end
diff --git a/features/steps/project/project_services.rb b/features/steps/project/project_services.rb
index b1668ff7207..a24100ff8c0 100644
--- a/features/steps/project/project_services.rb
+++ b/features/steps/project/project_services.rb
@@ -9,7 +9,8 @@ class ProjectServices < Spinach::FeatureSteps
Then 'I should see list of available services' do
page.should have_content 'Services'
- page.should have_content 'Jenkins'
+ page.should have_content 'Campfire'
+ page.should have_content 'Hipchat'
page.should have_content 'GitLab CI'
end
@@ -19,12 +20,42 @@ class ProjectServices < Spinach::FeatureSteps
And 'I fill gitlab-ci settings' do
check 'Active'
- fill_in 'Project URL', with: 'http://ci.gitlab.org/projects/3'
- fill_in 'CI Project token', with: 'verySecret'
+ fill_in 'Project url', with: 'http://ci.gitlab.org/projects/3'
+ fill_in 'Token', with: 'verySecret'
click_button 'Save'
end
Then 'I should see service settings saved' do
- find_field('Project URL').value.should == 'http://ci.gitlab.org/projects/3'
+ find_field('Project url').value.should == 'http://ci.gitlab.org/projects/3'
+ end
+
+ And 'I click hipchat service link' do
+ click_link 'Hipchat'
+ end
+
+ And 'I fill hipchat settings' do
+ check 'Active'
+ fill_in 'Room', with: 'gitlab'
+ fill_in 'Token', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ Then 'I should see hipchat service settings saved' do
+ find_field('Room').value.should == 'gitlab'
+ end
+
+
+ And 'I click pivotaltracker service link' do
+ click_link 'PivotalTracker'
+ end
+
+ And 'I fill pivotaltracker settings' do
+ check 'Active'
+ fill_in 'Token', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ Then 'I should see pivotaltracker service settings saved' do
+ find_field('Token').value.should == 'verySecret'
end
end
diff --git a/features/steps/project/project_snippets.rb b/features/steps/project/project_snippets.rb
new file mode 100644
index 00000000000..5082b31198a
--- /dev/null
+++ b/features/steps/project/project_snippets.rb
@@ -0,0 +1,100 @@
+class ProjectSnippets < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedNote
+ include SharedPaths
+
+ And 'project "Shop" have "Snippet one" snippet' do
+ create(:project_snippet,
+ title: "Snippet one",
+ content: "Test content",
+ file_name: "snippet.rb",
+ project: project,
+ author: project.users.first)
+ end
+
+ And 'project "Shop" have no "Snippet two" snippet' do
+ create(:snippet,
+ title: "Snippet two",
+ content: "Test content",
+ file_name: "snippet.rb",
+ author: project.users.first)
+ end
+
+ Given 'I click link "New Snippet"' do
+ click_link "Add new snippet"
+ end
+
+ Given 'I click link "Snippet one"' do
+ click_link "Snippet one"
+ end
+
+ Then 'I should see "Snippet one" in snippets' do
+ page.should have_content "Snippet one"
+ end
+
+ And 'I should not see "Snippet two" in snippets' do
+ page.should_not have_content "Snippet two"
+ end
+
+ And 'I should not see "Snippet one" in snippets' do
+ page.should_not have_content "Snippet one"
+ end
+
+ And 'I click link "Edit"' do
+ within ".file-title" do
+ click_link "Edit"
+ end
+ end
+
+ And 'I click link "Remove Snippet"' do
+ click_link "Remove snippet"
+ end
+
+ And 'I submit new snippet "Snippet three"' do
+ fill_in "project_snippet_title", :with => "Snippet three"
+ select "forever", :from => "project_snippet_expires_at"
+ fill_in "project_snippet_file_name", :with => "my_snippet.rb"
+ within('.file-editor') do
+ find(:xpath, "//input[@id='project_snippet_content']").set 'Content of snippet three'
+ end
+ click_button "Create snippet"
+ end
+
+ Then 'I should see snippet "Snippet three"' do
+ page.should have_content "Snippet three"
+ page.should have_content "Content of snippet three"
+ end
+
+ And 'I submit new title "Snippet new title"' do
+ fill_in "project_snippet_title", :with => "Snippet new title"
+ click_button "Save"
+ end
+
+ Then 'I should see "Snippet new title"' do
+ page.should have_content "Snippet new title"
+ end
+
+ And 'I leave a comment like "Good snippet!"' do
+ within('.js-main-target-form') do
+ fill_in "note_note", with: "Good snippet!"
+ click_button "Add Comment"
+ end
+ end
+
+ Then 'I should see comment "Good snippet!"' do
+ page.should have_content "Good snippet!"
+ end
+
+ And 'I visit snippet page "Snippet one"' do
+ visit project_snippet_path(project, project_snippet)
+ end
+
+ def project
+ @project ||= Project.find_by_name!("Shop")
+ end
+
+ def project_snippet
+ @project_snippet ||= ProjectSnippet.find_by_title!("Snippet One")
+ end
+end
diff --git a/features/steps/project/project_team_management.rb b/features/steps/project/project_team_management.rb
index 19352fe0ab8..efebba1be24 100644
--- a/features/steps/project/project_team_management.rb
+++ b/features/steps/project/project_team_management.rb
@@ -2,65 +2,56 @@ class ProjectTeamManagement < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
+ include Select2Helper
Then 'I should be able to see myself in team' do
page.should have_content(@user.name)
- page.should have_content(@user.email)
+ page.should have_content(@user.username)
end
And 'I should see "Sam" in team list' do
user = User.find_by_name("Sam")
page.should have_content(user.name)
- page.should have_content(user.email)
+ page.should have_content(user.username)
end
Given 'I click link "New Team Member"' do
- click_link "New Team Member"
+ click_link "New project member"
end
And 'I select "Mike" as "Reporter"' do
user = User.find_by_name("Mike")
+
+ select2(user.id, from: "#user_ids", multiple: true)
within "#new_team_member" do
- select user.name, :from => "user_ids"
- select "Reporter", :from => "project_access"
+ select "Reporter", from: "project_access"
end
click_button "Add users"
end
Then 'I should see "Mike" in team list as "Reporter"' do
- user = User.find_by_name("Mike")
- role_id = find(".user_#{user.id} #team_member_project_access").value
- role_id.should == UsersProject.access_roles["Reporter"].to_s
+ within ".access-reporter" do
+ page.should have_content('Mike')
+ end
end
Given 'I should see "Sam" in team list as "Developer"' do
- user = User.find_by_name("Sam")
- role_id = find(".user_#{user.id} #team_member_project_access").value
- role_id.should == UsersProject.access_roles["Developer"].to_s
+ within ".access-developer" do
+ page.should have_content('Sam')
+ end
end
And 'I change "Sam" role to "Reporter"' do
user = User.find_by_name("Sam")
- within ".user_#{user.id}" do
- select "Reporter", :from => "team_member_project_access"
+ within "#user_#{user.id}" do
+ select "Reporter", from: "team_member_project_access"
end
end
And 'I should see "Sam" in team list as "Reporter"' do
- user = User.find_by_name("Sam")
- role_id = find(".user_#{user.id} #team_member_project_access").value
- role_id.should == UsersProject.access_roles["Reporter"].to_s
- end
-
- Given 'I click link "Sam"' do
- click_link "Sam"
- end
-
- Then 'I should see "Sam" team profile' do
- user = User.find_by_name("Sam")
- page.should have_content(user.name)
- page.should have_content(user.email)
- page.should have_content("To team list")
+ within ".access-reporter" do
+ page.should have_content('Sam')
+ end
end
And 'I click link "Remove from team"' do
@@ -70,15 +61,15 @@ class ProjectTeamManagement < Spinach::FeatureSteps
And 'I should not see "Sam" in team list' do
user = User.find_by_name("Sam")
page.should_not have_content(user.name)
- page.should_not have_content(user.email)
+ page.should_not have_content(user.username)
end
And 'gitlab user "Mike"' do
- create(:user, :name => "Mike")
+ create(:user, name: "Mike")
end
And 'gitlab user "Sam"' do
- create(:user, :name => "Sam")
+ create(:user, name: "Sam")
end
And '"Sam" is "Shop" developer' do
@@ -88,7 +79,7 @@ class ProjectTeamManagement < Spinach::FeatureSteps
end
Given 'I own project "Website"' do
- @project = create(:project, :name => "Website")
+ @project = create(:project, name: "Website", namespace: @user.namespace)
@project.team << [@user, :master]
end
@@ -99,11 +90,18 @@ class ProjectTeamManagement < Spinach::FeatureSteps
end
And 'I click link "Import team from another project"' do
- click_link "Import team from another project"
+ click_link "Import members from another project"
end
When 'I submit "Website" project for import team' do
- select 'Website', from: 'source_project_id'
+ project = Project.find_by_name("Website")
+ select project.name_with_namespace, from: 'source_project_id'
click_button 'Import'
end
+
+ step 'I click cancel link for "Sam"' do
+ within "#user_#{User.find_by_name('Sam').id}" do
+ click_link('Remove user from team')
+ end
+ end
end
diff --git a/features/steps/project/project_wall.rb b/features/steps/project/project_wall.rb
index ba9d3533b2c..7c61580eb2c 100644
--- a/features/steps/project/project_wall.rb
+++ b/features/steps/project/project_wall.rb
@@ -3,4 +3,16 @@ class ProjectWall < Spinach::FeatureSteps
include SharedProject
include SharedNote
include SharedPaths
+
+
+ Given 'I write new comment "my special test message"' do
+ within(".wall-note-form") do
+ fill_in "note[note]", with: "my special test message"
+ click_button "Add Comment"
+ end
+ end
+
+ Then 'I should see project wall note "my special test message"' do
+ page.should have_content "my special test message"
+ end
end
diff --git a/features/steps/project/project_wiki.rb b/features/steps/project/project_wiki.rb
index 902e9ce158c..7aba412d751 100644
--- a/features/steps/project/project_wiki.rb
+++ b/features/steps/project/project_wiki.rb
@@ -4,17 +4,89 @@ class ProjectWiki < Spinach::FeatureSteps
include SharedNote
include SharedPaths
- Given 'I create Wiki page' do
- fill_in "Title", :with => 'Test title'
- fill_in "Content", :with => '[link test](test)'
- click_on "Save"
+ Given 'I click on the Cancel button' do
+ within(:css, ".form-actions") do
+ click_on "Cancel"
+ end
end
- Then 'I should see newly created wiki page' do
- page.should have_content "Test title"
+ Then 'I should be redirected back to the Edit Home Wiki page' do
+ url = URI.parse(current_url)
+ url.path.should == project_wiki_path(project, :home)
+ end
+
+ Given 'I create the Wiki Home page' do
+ fill_in "Content", with: '[link test](test)'
+ click_on "Create page"
+ end
+
+ Then 'I should see the newly created wiki page' do
+ page.should have_content "Home"
page.should have_content "link test"
click_link "link test"
page.should have_content "Editing page"
end
+
+ Given 'I have an existing Wiki page' do
+ wiki.create_page("existing", "content", :markdown, "first commit")
+ @page = wiki.find_page("existing")
+ end
+
+ And 'I browse to that Wiki page' do
+ visit project_wiki_path(project, @page)
+ end
+
+ And 'I click on the Edit button' do
+ click_on "Edit"
+ end
+
+ And 'I change the content' do
+ fill_in "Content", with: 'Updated Wiki Content'
+ click_on "Save changes"
+ end
+
+ Then 'I should see the updated content' do
+ page.should have_content "Updated Wiki Content"
+ end
+
+ Then 'I should be redirected back to that Wiki page' do
+ url = URI.parse(current_url)
+ url.path.should == project_wiki_path(project, @page)
+ end
+
+ And 'That page has two revisions' do
+ @page.update("new content", :markdown, "second commit")
+ end
+
+ And 'I click the History button' do
+ click_on "History"
+ end
+
+ Then 'I should see both revisions' do
+ page.should have_content current_user.name
+ page.should have_content "first commit"
+ page.should have_content "second commit"
+ end
+
+ And 'I click on the "Delete this page" button' do
+ click_on "Delete this page"
+ end
+
+ Then 'The page should be deleted' do
+ page.should have_content "Page was successfully deleted"
+ end
+
+ And 'I click on the "Pages" button' do
+ click_on "Pages"
+ end
+
+ Then 'I should see the existing page in the pages list' do
+ page.should have_content current_user.name
+ page.should have_content @page.title.titleize
+ end
+
+ def wiki
+ @gollum_wiki = GollumWiki.new(project, current_user)
+ end
end
diff --git a/features/steps/project/public_projects.rb b/features/steps/project/public_projects.rb
new file mode 100644
index 00000000000..7063e7d56ae
--- /dev/null
+++ b/features/steps/project/public_projects.rb
@@ -0,0 +1,9 @@
+class PublicProjects < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ Then 'I should see the list of public projects' do
+ page.should have_content "Public Projects"
+ end
+end
diff --git a/features/steps/public/projects_feature.rb b/features/steps/public/projects_feature.rb
new file mode 100644
index 00000000000..e9a4d56e36b
--- /dev/null
+++ b/features/steps/public/projects_feature.rb
@@ -0,0 +1,61 @@
+class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps
+ include SharedPaths
+
+ step 'I should see project "Community"' do
+ page.should have_content "Community"
+ end
+
+ step 'I should not see project "Enterprise"' do
+ page.should_not have_content "Enterprise"
+ end
+
+ step 'I should see project "Empty Public Project"' do
+ page.should have_content "Empty Public Project"
+ end
+
+ step 'I should see public project details' do
+ page.should have_content '32 branches'
+ page.should have_content '16 tags'
+ end
+
+ step 'I should see project readme' do
+ page.should have_content 'README.md'
+ end
+
+ step 'public project "Community"' do
+ create :project_with_code, name: 'Community', public: true, default_branch: 'master'
+ end
+
+ step 'public empty project "Empty Public Project"' do
+ create :project, name: 'Empty Public Project', public: true
+ end
+
+ step 'I visit empty project page' do
+ project = Project.find_by_name('Empty Public Project')
+ visit project_path(project)
+ end
+
+ step 'I visit project "Community" page' do
+ project = Project.find_by_name('Community')
+ visit project_path(project)
+ end
+
+ step 'I should see empty public project details' do
+ page.should have_content 'Git global setup'
+ end
+
+ step 'private project "Enterprise"' do
+ create :project, name: 'Enterprise'
+ end
+
+ step 'I should see project "Community" home page' do
+ page.should have_content 'Repo size is'
+ end
+
+ private
+
+ def project
+ @project ||= Project.find_by_name("Community")
+ end
+end
+
diff --git a/features/steps/shared/active_tab.rb b/features/steps/shared/active_tab.rb
index 446e3b9a8b3..d504fda3327 100644
--- a/features/steps/shared/active_tab.rb
+++ b/features/steps/shared/active_tab.rb
@@ -3,9 +3,9 @@ module SharedActiveTab
def ensure_active_main_tab(content)
if content == "Home"
- page.find('ul.main_menu li.active').should have_css('i.icon-home')
+ page.find('.main-nav li.active').should have_css('i.icon-home')
else
- page.find('ul.main_menu li.active').should have_content(content)
+ page.find('.main-nav li.active').should have_content(content)
end
end
@@ -13,11 +13,19 @@ module SharedActiveTab
page.find('div.content ul.nav-tabs li.active').should have_content(content)
end
+ def ensure_active_sub_nav(content)
+ page.find('div.content ul.nav-stacked-menu li.active').should have_content(content)
+ end
+
And 'no other main tabs should be active' do
- page.should have_selector('ul.main_menu li.active', count: 1)
+ page.should have_selector('.main-nav li.active', count: 1)
end
And 'no other sub tabs should be active' do
page.should have_selector('div.content ul.nav-tabs li.active', count: 1)
end
+
+ And 'no other sub navs should be active' do
+ page.should have_selector('div.content ul.nav-stacked-menu li.active', count: 1)
+ end
end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 04862338026..9c39a226e1b 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -3,15 +3,13 @@ module SharedDiffNote
Given 'I cancel the diff comment' do
within(".file") do
- find(".js-close-discussion-note-form").trigger("click")
+ find(".js-close-discussion-note-form").click
end
end
Given 'I delete a diff comment' do
- sleep 1
- within(".file") do
- first(".js-note-delete").trigger("click")
- end
+ find('.note').hover
+ find(".js-note-delete").click
end
Given 'I haven\'t written any diff comment text' do
@@ -21,37 +19,37 @@ module SharedDiffNote
end
Given 'I leave a diff comment like "Typo, please fix"' do
- find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click")
- within(".file") do
+ find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click
+ within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do
fill_in "note[note]", with: "Typo, please fix"
- #click_button("Add Comment")
find(".js-comment-button").trigger("click")
sleep 0.05
end
end
Given 'I preview a diff comment text like "Should fix it :smile:"' do
- find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click")
- within(".file") do
+ find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click
+ within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do
fill_in "note[note]", with: "Should fix it :smile:"
find(".js-note-preview-button").trigger("click")
end
end
Given 'I preview another diff comment text like "DRY this up"' do
- find("#586fb7c4e1add2d4d24e27566ed7064680098646_57_41.line_holder .js-add-diff-note-button").trigger("click")
- within(".file") do
+ find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_57_41"]').click
+
+ within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_57_41']") do
fill_in "note[note]", with: "DRY this up"
find(".js-note-preview-button").trigger("click")
end
end
Given 'I open a diff comment form' do
- find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click")
+ find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click
end
Given 'I open another diff comment form' do
- find("#586fb7c4e1add2d4d24e27566ed7064680098646_57_41.line_holder .js-add-diff-note-button").trigger("click")
+ find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_57_41"]').click
end
Given 'I write a diff comment like ":-1: I don\'t like this"' do
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 5dcc75f9165..da08da9420d 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -2,8 +2,8 @@ module SharedNote
include Spinach::DSL
Given 'I delete a comment' do
- sleep 1
- first(".js-note-delete").trigger("click")
+ find('.note').hover
+ find(".js-note-delete").click
end
Given 'I haven\'t written any comment text' do
@@ -97,21 +97,6 @@ module SharedNote
end
end
-
-
- # Wall
-
- Given 'I write new comment "my special test message"' do
- within(".js-main-target-form") do
- fill_in "note[note]", with: "my special test message"
- click_button "Add Comment"
- end
- end
-
- Then 'I should see project wall note "my special test message"' do
- page.should have_content "my special test message"
- end
-
Then 'I should see comment "XML attached"' do
within(".note") do
page.should have_content("XML attached")
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index a8e68012d05..c30eccce1c5 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -1,7 +1,7 @@
module SharedPaths
include Spinach::DSL
- When 'I visit new project page' do
+ step 'I visit new project page' do
visit new_project_path
end
@@ -9,23 +9,23 @@ module SharedPaths
# Group
# ----------------------------------------
- When 'I visit group page' do
+ step 'I visit group page' do
visit group_path(current_group)
end
- When 'I visit group issues page' do
+ step 'I visit group issues page' do
visit issues_group_path(current_group)
end
- When 'I visit group merge requests page' do
+ step 'I visit group merge requests page' do
visit merge_requests_group_path(current_group)
end
- When 'I visit group people page' do
- visit people_group_path(current_group)
+ step 'I visit group members page' do
+ visit members_group_path(current_group)
end
- When 'I visit group settings page' do
+ step 'I visit group settings page' do
visit edit_group_path(current_group)
end
@@ -33,27 +33,27 @@ module SharedPaths
# Dashboard
# ----------------------------------------
- Given 'I visit dashboard page' do
+ step 'I visit dashboard page' do
visit dashboard_path
end
- Given 'I visit dashboard projects page' do
+ step 'I visit dashboard projects page' do
visit projects_dashboard_path
end
- Given 'I visit dashboard issues page' do
+ step 'I visit dashboard issues page' do
visit issues_dashboard_path
end
- Given 'I visit dashboard merge requests page' do
+ step 'I visit dashboard merge requests page' do
visit merge_requests_dashboard_path
end
- Given 'I visit dashboard search page' do
+ step 'I visit dashboard search page' do
visit search_path
end
- Given 'I visit dashboard help page' do
+ step 'I visit dashboard help page' do
visit help_path
end
@@ -61,23 +61,23 @@ module SharedPaths
# Profile
# ----------------------------------------
- Given 'I visit profile page' do
+ step 'I visit profile page' do
visit profile_path
end
- Given 'I visit profile account page' do
+ step 'I visit profile account page' do
visit account_profile_path
end
- Given 'I visit profile SSH keys page' do
- visit keys_path
+ step 'I visit profile SSH keys page' do
+ visit profile_keys_path
end
- Given 'I visit profile design page' do
+ step 'I visit profile design page' do
visit design_profile_path
end
- Given 'I visit profile history page' do
+ step 'I visit profile history page' do
visit history_profile_path
end
@@ -85,35 +85,35 @@ module SharedPaths
# Admin
# ----------------------------------------
- Given 'I visit admin page' do
+ step 'I visit admin page' do
visit admin_root_path
end
- Given 'I visit admin projects page' do
+ step 'I visit admin projects page' do
visit admin_projects_path
end
- Given 'I visit admin users page' do
+ step 'I visit admin users page' do
visit admin_users_path
end
- Given 'I visit admin logs page' do
+ step 'I visit admin logs page' do
visit admin_logs_path
end
- Given 'I visit admin hooks page' do
+ step 'I visit admin hooks page' do
visit admin_hooks_path
end
- Given 'I visit admin Resque page' do
- visit admin_resque_path
+ step 'I visit admin Resque page' do
+ visit admin_background_jobs_path
end
- And 'I visit admin groups page' do
+ step 'I visit admin groups page' do
visit admin_groups_path
end
- When 'I visit admin teams page' do
+ step 'I visit admin teams page' do
visit admin_teams_path
end
@@ -121,149 +121,193 @@ module SharedPaths
# Generic Project
# ----------------------------------------
- Given "I visit my project's home page" do
+ step "I visit my project's home page" do
visit project_path(@project)
end
- Given "I visit my project's files page" do
+ step "I visit my project's settings page" do
+ visit edit_project_path(@project)
+ end
+
+ step "I visit my project's files page" do
visit project_tree_path(@project, root_ref)
end
- Given "I visit my project's commits page" do
+ step "I visit my project's commits page" do
visit project_commits_path(@project, root_ref, {limit: 5})
end
- Given "I visit my project's commits page for a specific path" do
+ step "I visit my project's commits page for a specific path" do
visit project_commits_path(@project, root_ref + "/app/models/project.rb", {limit: 5})
end
- Given 'I visit my project\'s commits stats page' do
+ step 'I visit my project\'s commits stats page' do
visit stats_project_repository_path(@project)
end
- Given "I visit my project's network page" do
- # Stub Graph::JsonBuilder max_size to speed up test (10 commits vs. 650)
- Gitlab::Graph::JsonBuilder.stub(max_count: 10)
+ step "I visit my project's network page" do
+ # Stub Graph max_size to speed up test (10 commits vs. 650)
+ Network::Graph.stub(max_count: 10)
- visit project_graph_path(@project, root_ref)
+ visit project_network_path(@project, root_ref)
end
- Given "I visit my project's issues page" do
+ step "I visit my project's issues page" do
visit project_issues_path(@project)
end
- Given "I visit my project's merge requests page" do
+ step "I visit my project's merge requests page" do
visit project_merge_requests_path(@project)
end
- Given "I visit my project's wall page" do
- visit wall_project_path(@project)
+ step "I visit my project's wall page" do
+ visit project_wall_path(@project)
end
- Given "I visit my project's wiki page" do
- visit project_wiki_path(@project, :index)
+ step "I visit my project's wiki page" do
+ visit project_wiki_path(@project, :home)
end
- When 'I visit project hooks page' do
+ step 'I visit project hooks page' do
visit project_hooks_path(@project)
end
+ step 'I visit project deploy keys page' do
+ visit project_deploy_keys_path(@project)
+ end
+
# ----------------------------------------
# "Shop" Project
# ----------------------------------------
- And 'I visit project "Shop" page' do
- project = Project.find_by_name("Shop")
+ step 'I visit project "Shop" page' do
visit project_path(project)
end
- When 'I visit edit project "Shop" page' do
- project = Project.find_by_name("Shop")
+ step 'I visit project "Forked Shop" merge requests page' do
+ visit project_merge_requests_path(@forked_project)
+ end
+
+ step 'I visit edit project "Shop" page' do
visit edit_project_path(project)
end
- Given 'I visit project branches page' do
- visit branches_project_repository_path(@project)
+ step 'I visit project branches page' do
+ visit project_branches_path(@project)
end
- Given 'I visit compare refs page' do
+ step 'I visit compare refs page' do
visit project_compare_index_path(@project)
end
- Given 'I visit project commits page' do
+ step 'I visit project commits page' do
visit project_commits_path(@project, root_ref, {limit: 5})
end
- Given 'I visit project commits page for stable branch' do
+ step 'I visit project commits page for stable branch' do
visit project_commits_path(@project, 'stable', {limit: 5})
end
- Given 'I visit project source page' do
+ step 'I visit project source page' do
visit project_tree_path(@project, root_ref)
end
- Given 'I visit blob file from repo' do
- visit project_tree_path(@project, File.join(ValidCommit::ID, ValidCommit::BLOB_FILE_PATH))
+ step 'I visit blob file from repo' do
+ visit project_blob_path(@project, File.join(ValidCommit::ID, ValidCommit::BLOB_FILE_PATH))
end
- Given 'I visit project source page for "8470d70"' do
+ step 'I visit project source page for "8470d70"' do
visit project_tree_path(@project, "8470d70")
end
- Given 'I visit project tags page' do
- visit tags_project_repository_path(@project)
+ step 'I visit project tags page' do
+ visit project_tags_path(@project)
end
- Given 'I visit project commit page' do
+ step 'I visit project commit page' do
visit project_commit_path(@project, ValidCommit::ID)
end
- And 'I visit project "Shop" issues page' do
- visit project_issues_path(Project.find_by_name("Shop"))
+ step 'I visit project "Shop" issues page' do
+ visit project_issues_path(project)
end
- Given 'I visit issue page "Release 0.4"' do
+ step 'I visit issue page "Release 0.4"' do
issue = Issue.find_by_title("Release 0.4")
visit project_issue_path(issue.project, issue)
end
- Given 'I visit project "Shop" labels page' do
- visit project_labels_path(Project.find_by_name("Shop"))
+ step 'I visit project "Shop" labels page' do
+ visit project_labels_path(project)
end
- Given 'I visit merge request page "Bug NS-04"' do
+ step 'I visit merge request page "Bug NS-04"' do
mr = MergeRequest.find_by_title("Bug NS-04")
- visit project_merge_request_path(mr.project, mr)
+ visit project_merge_request_path(mr.target_project, mr)
end
- Given 'I visit merge request page "Bug NS-05"' do
+ step 'I visit merge request page "Bug NS-05"' do
mr = MergeRequest.find_by_title("Bug NS-05")
- visit project_merge_request_path(mr.project, mr)
+ visit project_merge_request_path(mr.target_project, mr)
+ end
+
+ step 'I visit project "Shop" merge requests page' do
+ visit project_merge_requests_path(project)
+ end
+
+ step 'I visit forked project "Shop" merge requests page' do
+ visit project_merge_requests_path(project)
+ end
+
+ step 'I visit project "Shop" milestones page' do
+ visit project_milestones_path(project)
+ end
+
+ step 'I visit project "Shop" team page' do
+ visit project_team_index_path(project)
end
- And 'I visit project "Shop" merge requests page' do
- visit project_merge_requests_path(Project.find_by_name("Shop"))
+ step 'I visit project "Shop" wall page' do
+ visit project_wall_path(project)
end
- Given 'I visit project "Shop" milestones page' do
- @project = Project.find_by_name("Shop")
- visit project_milestones_path(@project)
+ step 'I visit project wiki page' do
+ visit project_wiki_path(@project, :home)
end
- Then 'I visit project "Shop" team page' do
- visit project_team_index_path(Project.find_by_name("Shop"))
+ # ----------------------------------------
+ # Public Projects
+ # ----------------------------------------
+
+ step 'I visit the public projects area' do
+ visit public_root_path
end
- Then 'I visit project "Shop" wall page' do
- project = Project.find_by_name("Shop")
- visit wall_project_path(project)
+ step 'I visit public page for "Community" project' do
+ visit public_project_path(Project.find_by_name("Community"))
end
- Given 'I visit project wiki page' do
- visit project_wiki_path(@project, :index)
+ # ----------------------------------------
+ # Snippets
+ # ----------------------------------------
+
+ Given 'I visit project "Shop" snippets page' do
+ visit project_snippets_path(project)
+ end
+
+ Given 'I visit snippets page' do
+ visit snippets_path
+ end
+
+ Given 'I visit new snippet page' do
+ visit new_snippet_path
end
def root_ref
@project.repository.root_ref
end
+
+ def project
+ project = Project.find_by_name!("Shop")
+ end
end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index 5e61a4132c0..c5d8b62bfe7 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -3,13 +3,14 @@ module SharedProject
# Create a project without caring about what it's called
And "I own a project" do
- @project = create(:project)
+ @project = create(:project_with_code, namespace: @user.namespace)
@project.team << [@user, :master]
end
# Create a specific project called "Shop"
And 'I own project "Shop"' do
- @project = create(:project, name: "Shop")
+ @project = Project.find_by_name "Shop"
+ @project ||= create(:project_with_code, name: "Shop", namespace: @user.namespace)
@project.team << [@user, :master]
end
@@ -41,7 +42,7 @@ module SharedProject
Then 'I should see project "Shop" activity feed' do
project = Project.find_by_name("Shop")
- page.should have_content "#{@user.name} pushed new branch new_design at #{project.name}"
+ page.should have_content "#{@user.name} pushed new branch new_design at #{project.name_with_namespace}"
end
Then 'I should see project settings' do
@@ -50,6 +51,10 @@ module SharedProject
page.should have_content("Features:")
end
+ Then 'page status code should be 404' do
+ page.status_code.should == 404
+ end
+
def current_project
@project ||= Project.first
end
diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb
new file mode 100644
index 00000000000..543e43196a5
--- /dev/null
+++ b/features/steps/shared/snippet.rb
@@ -0,0 +1,21 @@
+module SharedSnippet
+ include Spinach::DSL
+
+ And 'I have public "Personal snippet one" snippet' do
+ create(:personal_snippet,
+ title: "Personal snippet one",
+ content: "Test content",
+ file_name: "snippet.rb",
+ private: false,
+ author: current_user)
+ end
+
+ And 'I have private "Personal snippet private" snippet' do
+ create(:personal_snippet,
+ title: "Personal snippet private",
+ content: "Provate content",
+ file_name: "private_snippet.rb",
+ private: true,
+ author: current_user)
+ end
+end
diff --git a/features/steps/snippets/discover_snippets.rb b/features/steps/snippets/discover_snippets.rb
new file mode 100644
index 00000000000..3afe019adf6
--- /dev/null
+++ b/features/steps/snippets/discover_snippets.rb
@@ -0,0 +1,17 @@
+class DiscoverSnippets < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedSnippet
+
+ Then 'I should see "Personal snippet one" in snippets' do
+ page.should have_content "Personal snippet one"
+ end
+
+ And 'I should not see "Personal snippet private" in snippets' do
+ page.should_not have_content "Personal snippet private"
+ end
+
+ def snippet
+ @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
+ end
+end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
new file mode 100644
index 00000000000..bbdf5b97c84
--- /dev/null
+++ b/features/steps/snippets/snippets.rb
@@ -0,0 +1,64 @@
+class SnippetsFeature < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+ include SharedSnippet
+
+ Given 'I click link "Personal snippet one"' do
+ click_link "Personal snippet one"
+ end
+
+ And 'I should not see "Personal snippet one" in snippets' do
+ page.should_not have_content "Personal snippet one"
+ end
+
+ And 'I click link "Edit"' do
+ within ".file-title" do
+ click_link "Edit"
+ end
+ end
+
+ And 'I click link "Destroy"' do
+ click_link "Destroy"
+ end
+
+ And 'I submit new snippet "Personal snippet three"' do
+ fill_in "personal_snippet_title", :with => "Personal snippet three"
+ fill_in "personal_snippet_file_name", :with => "my_snippet.rb"
+ within('.file-editor') do
+ find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of snippet three'
+ end
+ click_button "Create snippet"
+ end
+
+ Then 'I should see snippet "Personal snippet three"' do
+ page.should have_content "Personal snippet three"
+ page.should have_content "Content of snippet three"
+ end
+
+ And 'I submit new title "Personal snippet new title"' do
+ fill_in "personal_snippet_title", :with => "Personal snippet new title"
+ click_button "Save"
+ end
+
+ Then 'I should see "Personal snippet new title"' do
+ page.should have_content "Personal snippet new title"
+ end
+
+ And 'I uncheck "Private" checkbox' do
+ find(:xpath, "//input[@id='personal_snippet_private']").set true
+ click_button "Save"
+ end
+
+ Then 'I should see "Personal snippet one" public' do
+ page.should have_no_xpath("//i[@class='public-snippet']")
+ end
+
+ And 'I visit snippet page "Personal snippet one"' do
+ visit snippet_path(snippet)
+ end
+
+ def snippet
+ @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
+ end
+end
diff --git a/features/steps/snippets/user_snippets.rb b/features/steps/snippets/user_snippets.rb
new file mode 100644
index 00000000000..15d6da6db3d
--- /dev/null
+++ b/features/steps/snippets/user_snippets.rb
@@ -0,0 +1,41 @@
+class UserSnippets < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedSnippet
+
+ Given 'I visit my snippets page' do
+ visit user_snippets_path(current_user)
+ end
+
+ Then 'I should see "Personal snippet one" in snippets' do
+ page.should have_content "Personal snippet one"
+ end
+
+ And 'I should see "Personal snippet private" in snippets' do
+ page.should have_content "Personal snippet private"
+ end
+
+ Then 'I should not see "Personal snippet one" in snippets' do
+ page.should_not have_content "Personal snippet one"
+ end
+
+ And 'I should not see "Personal snippet private" in snippets' do
+ page.should_not have_content "Personal snippet private"
+ end
+
+ Given 'I click "Public" filter' do
+ within('.nav-stacked') do
+ click_link "Public"
+ end
+ end
+
+ Given 'I click "Private" filter' do
+ within('.nav-stacked') do
+ click_link "Private"
+ end
+ end
+
+ def snippet
+ @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
+ end
+end
diff --git a/features/steps/userteams/userteams.rb b/features/steps/userteams/userteams.rb
deleted file mode 100644
index be83b4bac05..00000000000
--- a/features/steps/userteams/userteams.rb
+++ /dev/null
@@ -1,254 +0,0 @@
-class Userteams < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
-
- When 'I do not have teams with me' do
- UserTeam.with_member(current_user).destroy_all
- end
-
- Then 'I should see dashboard page without teams info block' do
- page.has_no_css?(".teams-box").must_equal true
- end
-
- When 'I have teams with my membership' do
- team = create :user_team, owner: current_user
- team.add_member(current_user, UserTeam.access_roles["Master"], true)
- end
-
- Then 'I should see dashboard page with teams information block' do
- page.should have_css(".teams-box")
- end
-
- When 'exist user teams' do
- team = create :user_team
- team.add_member(current_user, UserTeam.access_roles["Master"], true)
- end
-
- And 'I click on "All teams" link' do
- click_link("All Teams")
- end
-
- Then 'I should see "All teams" page' do
- current_path.should == teams_path
- end
-
- And 'I should see exist teams in teams list' do
- team = UserTeam.last
- find_in_list(".teams_list tr", team).must_equal true
- end
-
- When 'I click to "New team" link' do
- click_link("New Team")
- end
-
- And 'I submit form with new team info' do
- fill_in 'name', with: 'gitlab'
- click_button 'Create team'
- end
-
- Then 'I should be redirected to new team page' do
- team = UserTeam.last
- current_path.should == team_path(team)
- end
-
- When 'I have teams with projects and members' do
- team = create :user_team, owner: current_user
- @project = create :project
- team.add_member(current_user, UserTeam.access_roles["Master"], true)
- team.assign_to_project(@project, UserTeam.access_roles["Master"])
- @event = create(:closed_issue_event, project: @project)
- end
-
- When 'I visit team page' do
- visit team_path(UserTeam.last)
- end
-
- Then 'I should see projects list' do
- page.should have_css(".projects_box")
- projects_box = find(".projects_box")
- projects_box.should have_content(@project.name)
- end
-
- And 'project from team has issues assigned to me' do
- team = UserTeam.last
- team.projects.each do |project|
- project.issues << create(:issue, assignee: current_user)
- end
- end
-
- When 'I visit team issues page' do
- team = UserTeam.last
- visit issues_team_path(team)
- end
-
- Then 'I should see issues from this team assigned to me' do
- team = UserTeam.last
- team.projects.each do |project|
- project.issues.assigned(current_user).each do |issue|
- page.should have_content issue.title
- end
- end
- end
-
- Given 'I have team with projects and members' do
- team = create :user_team, owner: current_user
- project = create :project
- user = create :user
- team.add_member(current_user, UserTeam.access_roles["Master"], true)
- team.add_member(user, UserTeam.access_roles["Developer"], false)
- team.assign_to_project(project, UserTeam.access_roles["Master"])
- end
-
- Given 'project from team has issues assigned to teams members' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- project.issues << create(:issue, assignee: member)
- end
- end
- end
-
- Then 'I should see issues from this team assigned to teams members' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- project.issues.assigned(member).each do |issue|
- page.should have_content issue.title
- end
- end
- end
- end
-
- Given 'project from team has merge requests assigned to me' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- 3.times { project.merge_requests << create(:merge_request, assignee: member) }
- end
- end
- end
-
- When 'I visit team merge requests page' do
- team = UserTeam.last
- visit merge_requests_team_path(team)
- end
-
- Then 'I should see merge requests from this team assigned to me' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- project.issues.assigned(member).each do |merge_request|
- page.should have_content merge_request.title
- end
- end
- end
- end
-
- Given 'project from team has merge requests assigned to team members' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- 3.times { project.merge_requests << create(:merge_request, assignee: member) }
- end
- end
- end
-
- Then 'I should see merge requests from this team assigned to me' do
- team = UserTeam.last
- team.projects.each do |project|
- team.members.each do |member|
- project.issues.assigned(member).each do |merge_request|
- page.should have_content merge_request.title
- end
- end
- end
- end
-
- Given 'I have new user "John"' do
- create :user, name: "John"
- end
-
- When 'I visit team people page' do
- team = UserTeam.last
- visit team_members_path(team)
- end
-
- And 'I select user "John" from list with role "Reporter"' do
- user = User.find_by_name("John")
- within "#team_members" do
- select user.name, :from => "user_ids"
- select "Reporter", :from => "default_project_access"
- end
- click_button "Add"
- end
-
- Then 'I should see user "John" in team list' do
- user = User.find_by_name("John")
- team_members_list = find(".team-table")
- team_members_list.should have_content user.name
- end
-
- And 'I have my own project without teams' do
- @project = create :project, namespace: current_user.namespace
- end
-
- And 'I visit my team page' do
- team = UserTeam.where(owner_id: current_user.id).last
- visit team_path(team)
- end
-
- When 'I click on link "Projects"' do
- click_link "Projects"
- end
-
- And 'I click link "Assign project to Team"' do
- click_link "Assign project to Team"
- end
-
- Then 'I should see form with my own project in avaliable projects list' do
- projects_select = find("#project_ids")
- projects_select.should have_content(@project.name)
- end
-
- When 'I submit form with selected project and max access' do
- within "#assign_projects" do
- select @project.name_with_namespace, :from => "project_ids"
- select "Reporter", :from => "greatest_project_access"
- end
- click_button "Add"
- end
-
- Then 'I should see my own project in team projects list' do
- projects = find(".projects-table")
- projects.should have_content(@project.name)
- end
-
- When 'I click link "New Team Member"' do
- click_link "New Team Member"
- end
-
- protected
-
- def current_team
- @user_team ||= UserTeam.first
- end
-
- def project
- current_team.projects.first
- end
-
- def assigned_to_user key, user
- project.send(key).where(assignee_id: user)
- end
-
- def find_in_list(selector, item)
- members_list = all(selector)
- entered = false
- members_list.each do |member_item|
- entered = true if member_item.has_content?(item.name)
- end
- entered
- end
-
-end
diff --git a/features/support/env.rb b/features/support/env.rb
index da40b38b79c..8798e62ea72 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -1,5 +1,10 @@
require 'simplecov' unless ENV['CI']
+if ENV['TRAVIS']
+ require 'coveralls'
+ Coveralls.wear!
+end
+
ENV['RAILS_ENV'] = 'test'
require './config/environment'
@@ -9,7 +14,7 @@ require 'spinach/capybara'
require 'sidekiq/testing/inline'
-%w(stubbed_repository valid_commit).each do |f|
+%w(valid_commit big_commits select2_helper chosen_helper test_env).each do |f|
require Rails.root.join('spec', 'support', f)
end
@@ -21,19 +26,20 @@ WebMock.allow_net_connect!
#
require 'capybara/poltergeist'
Capybara.javascript_driver = :poltergeist
+Capybara.register_driver :poltergeist do |app|
+ Capybara::Poltergeist::Driver.new(app, :js_errors => false, :timeout => 60)
+end
Spinach.hooks.on_tag("javascript") do
::Capybara.current_driver = ::Capybara.javascript_driver
- ::Capybara.default_wait_time = 5
end
-
+Capybara.default_wait_time = 60
+Capybara.ignore_hidden_elements = false
DatabaseCleaner.strategy = :truncation
Spinach.hooks.before_scenario do
- # Use tmp dir for FS manipulations
- Gitlab.config.gitlab_shell.stub(repos_path: Rails.root.join('tmp', 'test-git-base-path'))
- FileUtils.rm_rf Gitlab.config.gitlab_shell.repos_path
- FileUtils.mkdir_p Gitlab.config.gitlab_shell.repos_path
+ TestEnv.setup_stubs
+ DatabaseCleaner.start
end
Spinach.hooks.after_scenario do
@@ -41,6 +47,7 @@ Spinach.hooks.after_scenario do
end
Spinach.hooks.before_run do
+ TestEnv.init(mailer: false, init_repos: true, repos: false)
RSpec::Mocks::setup self
include FactoryGirl::Syntax::Methods
diff --git a/features/teams/team.feature b/features/teams/team.feature
deleted file mode 100644
index 9255e0daadb..00000000000
--- a/features/teams/team.feature
+++ /dev/null
@@ -1,69 +0,0 @@
-Feature: UserTeams
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And project "Shop" has push event
-
- Scenario: No teams, no dashboard info block
- When I do not have teams with me
- And I visit dashboard page
- Then I should see dashboard page without teams info block
-
- Scenario: I should see teams info block
- When I have teams with my membership
- And I visit dashboard page
- Then I should see dashboard page with teams information block
-
- Scenario: I should can create new team
- When I have teams with my membership
- And I visit dashboard page
- When I click to "New team" link
- And I submit form with new team info
- Then I should be redirected to new team page
-
- Scenario: I should see team dashboard list
- When I have teams with projects and members
- When I visit team page
- Then I should see projects list
-
- Scenario: I should see team issues list
- Given I have team with projects and members
- And project from team has issues assigned to me
- When I visit team issues page
- Then I should see issues from this team assigned to me
-
- Scenario: I should see teams members issues list
- Given I have team with projects and members
- Given project from team has issues assigned to teams members
- When I visit team issues page
- Then I should see issues from this team assigned to teams members
-
- Scenario: I should see team merge requests list
- Given I have team with projects and members
- Given project from team has merge requests assigned to me
- When I visit team merge requests page
- Then I should see merge requests from this team assigned to me
-
- Scenario: I should see teams members merge requests list
- Given I have team with projects and members
- Given project from team has merge requests assigned to team members
- When I visit team merge requests page
- Then I should see merge requests from this team assigned to me
-
- Scenario: I should add user to projects in Team
- Given I have team with projects and members
- Given I have new user "John"
- When I visit team people page
- When I click link "New Team Member"
- And I select user "John" from list with role "Reporter"
- Then I should see user "John" in team list
-
- Scenario: I should assign my team to my own project
- Given I have team with projects and members
- And I have my own project without teams
- And I visit my team page
- When I click on link "Projects"
- And I click link "Assign project to Team"
- Then I should see form with my own project in avaliable projects list
- When I submit form with selected project and max access
- Then I should see my own project in team projects list
diff --git a/lib/api.rb b/lib/api.rb
deleted file mode 100644
index d9dce7c70cc..00000000000
--- a/lib/api.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file}
-
-module Gitlab
- class API < Grape::API
- version 'v3', using: :path
-
- rescue_from ActiveRecord::RecordNotFound do
- rack_response({'message' => '404 Not found'}.to_json, 404)
- end
-
- format :json
- error_format :json
- helpers APIHelpers
-
- mount Groups
- mount Users
- mount Projects
- mount Issues
- mount Milestones
- mount Session
- mount MergeRequests
- mount Notes
- mount Internal
- end
-end
diff --git a/lib/api/api.rb b/lib/api/api.rb
new file mode 100644
index 00000000000..c4c9f166db1
--- /dev/null
+++ b/lib/api/api.rb
@@ -0,0 +1,42 @@
+Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file}
+
+module API
+ class API < Grape::API
+ version 'v3', using: :path
+
+ rescue_from ActiveRecord::RecordNotFound do
+ rack_response({'message' => '404 Not found'}.to_json, 404)
+ end
+
+ rescue_from :all do |exception|
+ # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
+ # why is this not wrapped in something reusable?
+ trace = exception.backtrace
+
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << trace.join("\n ")
+
+ API.logger.add Logger::FATAL, message
+ rack_response({'message' => '500 Internal Server Error'}, 500)
+ end
+
+ format :json
+ helpers APIHelpers
+
+ mount Groups
+ mount Users
+ mount Projects
+ mount Repositories
+ mount Issues
+ mount Milestones
+ mount Session
+ mount MergeRequests
+ mount Notes
+ mount Internal
+ mount SystemHooks
+ mount ProjectSnippets
+ mount DeployKeys
+ mount ProjectHooks
+ end
+end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
new file mode 100644
index 00000000000..55c947eb176
--- /dev/null
+++ b/lib/api/deploy_keys.rb
@@ -0,0 +1,84 @@
+module API
+ # Projects API
+ class DeployKeys < 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 specific project's keys
+ #
+ # Example Request:
+ # GET /projects/:id/keys
+ get ":id/keys" do
+ present user_project.deploy_keys, with: Entities::SSHKey
+ end
+
+ # Get single key owned by currently authenticated user
+ #
+ # Example Request:
+ # GET /projects/:id/keys/:id
+ get ":id/keys/:key_id" do
+ key = user_project.deploy_keys.find params[:key_id]
+ present key, with: Entities::SSHKey
+ end
+
+ # Add new ssh key to currently authenticated user
+ # If deploy key already exists - it will be joined to project
+ # but only if original one was is accessible by same user
+ #
+ # Parameters:
+ # key (required) - New SSH Key
+ # title (required) - New SSH Key's title
+ # Example Request:
+ # POST /projects/:id/keys
+ post ":id/keys" do
+ attrs = attributes_for_keys [:title, :key]
+
+ if attrs[:key].present?
+ attrs[:key].strip!
+
+ # check if key already exist in project
+ key = user_project.deploy_keys.find_by_key(attrs[:key])
+ if key
+ present key, with: Entities::SSHKey
+ return
+ end
+
+ # Check for available deploy keys in other projects
+ key = current_user.accessible_deploy_keys.find_by_key(attrs[:key])
+ if key
+ user_project.deploy_keys << key
+ present key, with: Entities::SSHKey
+ return
+ end
+ end
+
+ key = DeployKey.new attrs
+
+ if key.valid? && user_project.deploy_keys << key
+ present key, with: Entities::SSHKey
+ else
+ not_found!
+ end
+ end
+
+ # Delete existed ssh key of currently authenticated user
+ #
+ # Example Request:
+ # DELETE /projects/:id/keys/:id
+ delete ":id/keys/:key_id" do
+ key = user_project.deploy_keys.find params[:key_id]
+ key.destroy
+ end
+ end
+ end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index c1873d87b55..1f35e9ec5fc 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1,46 +1,78 @@
-module Gitlab
+module API
module Entities
class User < Grape::Entity
expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter,
- :dark_scheme, :theme_id, :blocked, :created_at, :extern_uid, :provider
+ :theme_id, :color_scheme_id, :state, :created_at, :extern_uid, :provider
+ end
+
+ class UserSafe < Grape::Entity
+ expose :name
end
class UserBasic < Grape::Entity
- expose :id, :username, :email, :name, :blocked, :created_at
+ expose :id, :username, :email, :name, :state, :created_at
end
- class UserLogin < UserBasic
+ class UserLogin < User
expose :private_token
+ expose :is_admin?, as: :is_admin
+ expose :can_create_group?, as: :can_create_group
+ expose :can_create_project?, as: :can_create_project
+ expose :can_create_team?, as: :can_create_team
end
class Hook < Grape::Entity
expose :id, :url, :created_at
end
+ class ForkedFromProject < Grape::Entity
+ expose :id
+ expose :name, :name_with_namespace
+ expose :path, :path_with_namespace
+ end
+
class Project < Grape::Entity
- expose :id, :name, :description, :default_branch
+ expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url
expose :owner, using: Entities::UserBasic
- expose :private_flag, as: :private
+ expose :name, :name_with_namespace
expose :path, :path_with_namespace
- expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at
+ expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at, :public
expose :namespace
+ expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
end
class ProjectMember < UserBasic
- expose :project_access, :as => :access_level do |user, options|
+ expose :project_access, as: :access_level do |user, options|
options[:project].users_projects.find_by_user_id(user.id).project_access
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
-
+
class GroupDetail < Group
expose :projects, using: Entities::Project
end
-
+ class GroupMember < UserBasic
+ expose :group_access, as: :access_level do |user, options|
+ options[:group].users_groups.find_by_user_id(user.id).group_access
+ end
+ end
+
class RepoObject < Grape::Entity
expose :name, :commit
expose :protected do |repo, options|
@@ -63,7 +95,7 @@ module Gitlab
class Milestone < Grape::Entity
expose :id
expose (:project_id) {|milestone| milestone.project.id}
- expose :title, :description, :due_date, :closed, :updated_at, :created_at
+ expose :title, :description, :due_date, :state, :updated_at, :created_at
end
class Issue < Grape::Entity
@@ -73,7 +105,7 @@ module Gitlab
expose :label_list, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
- expose :closed, :updated_at, :created_at
+ expose :state, :updated_at, :created_at
end
class SSHKey < Grape::Entity
@@ -81,13 +113,15 @@ module Gitlab
end
class MergeRequest < Grape::Entity
- expose :id, :target_branch, :source_branch, :project_id, :title, :closed, :merged
+ expose :id, :target_branch, :source_branch, :title, :state
+ expose :target_project_id, as: :project_id
expose :author, :assignee, using: Entities::UserBasic
end
class Note < Grape::Entity
expose :id
expose :note, as: :body
+ expose :attachment_identifier, as: :attachment
expose :author, using: Entities::UserBasic
expose :created_at
end
@@ -96,5 +130,11 @@ module Gitlab
expose :note
expose :author, using: Entities::UserBasic
end
+
+ class Event < Grape::Entity
+ expose :title, :project_id, :action_name
+ expose :target_id, :target_type, :author_id
+ expose :data, :target_title
+ end
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index a67caef0bc5..396554404af 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -1,9 +1,23 @@
-module Gitlab
+module API
# groups API
class Groups < Grape::API
before { authenticate! }
resource :groups do
+ helpers do
+ def find_group(id)
+ group = Group.find(id)
+ if current_user.admin or current_user.groups.include? group
+ group
+ else
+ render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403)
+ end
+ end
+ def validate_access_level?(level)
+ Gitlab::Access.options_with_owner.values.include? level.to_i
+ end
+ end
+
# Get a groups list
#
# Example Request:
@@ -20,12 +34,14 @@ module Gitlab
# Create group. Available only for admin
#
# Parameters:
- # name (required) - Name
- # path (required) - Path
+ # name (required) - The name of the group
+ # path (required) - The path of the group
# Example Request:
# POST /groups
post do
authenticated_as_admin!
+ required_attributes! [:name, :path]
+
attrs = attributes_for_keys [:name, :path]
@group = Group.new(attrs)
@group.owner = current_user
@@ -44,13 +60,79 @@ module Gitlab
# Example Request:
# GET /groups/:id
get ":id" do
+ group = find_group(params[:id])
+ present group, with: Entities::GroupDetail
+ end
+
+ # Transfer a project to the Group namespace
+ #
+ # Parameters:
+ # id - group id
+ # project_id - project id
+ # Example Request:
+ # POST /groups/:id/projects/:project_id
+ post ":id/projects/:project_id" do
+ authenticated_as_admin!
@group = Group.find(params[:id])
- if current_user.admin or current_user.groups.include? @group
- present @group, with: Entities::GroupDetail
+ project = Project.find(params[:project_id])
+ if project.transfer(@group)
+ present @group
else
not_found!
end
end
+
+ # Get a list of group members viewable by the authenticated user.
+ #
+ # Example Request:
+ # GET /groups/:id/members
+ get ":id/members" do
+ group = find_group(params[:id])
+ members = group.users_groups
+ users = (paginate members).collect(&:user)
+ present users, with: Entities::GroupMember, group: group
+ end
+
+ # Add a user to the list of group members
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ # access_level (required) - Project access level
+ # Example Request:
+ # POST /groups/:id/members
+ post ":id/members" do
+ required_attributes! [:user_id, :access_level]
+ unless validate_access_level?(params[:access_level])
+ render_api_error!("Wrong access level", 422)
+ end
+ group = find_group(params[:id])
+ if group.users_groups.find_by_user_id(params[:user_id])
+ render_api_error!("Already exists", 409)
+ end
+ group.add_users([params[:user_id]], params[:access_level])
+ member = group.users_groups.find_by_user_id(params[:user_id])
+ present member.user, with: Entities::GroupMember, group: group
+ end
+
+ # Remove member.
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ #
+ # Example Request:
+ # DELETE /groups/:id/members/:user_id
+ delete ":id/members/:user_id" do
+ group = find_group(params[:id])
+ member = group.users_groups.find_by_user_id(params[:user_id])
+ if member.nil?
+ render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
+ else
+ member.destroy
+ end
+ end
+
end
end
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 6bd8111c2b2..4f189f35196 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -1,16 +1,43 @@
-module Gitlab
+module API
module APIHelpers
+ PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN"
+ PRIVATE_TOKEN_PARAM = :private_token
+ SUDO_HEADER ="HTTP_SUDO"
+ SUDO_PARAM = :sudo
+
def current_user
- @current_user ||= User.find_by_authentication_token(params[:private_token] || env["HTTP_PRIVATE_TOKEN"])
+ @current_user ||= User.find_by_authentication_token(params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER])
+ identifier = sudo_identifier()
+ # If the sudo is the current user do nothing
+ if (identifier && !(@current_user.id == identifier || @current_user.username == identifier))
+ render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin?
+ begin
+ @current_user = User.by_username_or_id(identifier)
+ rescue => ex
+ not_found!("No user id or username for: #{identifier}")
+ end
+ not_found!("No user id or username for: #{identifier}") if current_user.nil?
+ end
+ @current_user
+ end
+
+ def sudo_identifier()
+ identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER]
+ # Regex for integers
+ if (!!(identifier =~ /^[0-9]+$/))
+ identifier.to_i
+ else
+ identifier
+ end
end
def user_project
- @project ||= find_project
+ @project ||= find_project(params[:id])
@project || not_found!
end
- def find_project
- project = Project.find_by_id(params[:id]) || Project.find_with_namespace(params[:id])
+ def find_project(id)
+ project = Project.find_by_id(id) || Project.find_with_namespace(id)
if project && can?(current_user, :read_project, project)
project
@@ -41,6 +68,17 @@ module Gitlab
abilities.allowed?(object, action, subject)
end
+ # Checks the occurrences of required attributes, each attribute must be present in the params hash
+ # or a Bad Request error is invoked.
+ #
+ # Parameters:
+ # keys (required) - A hash consisting of keys that must be present
+ def required_attributes!(keys)
+ keys.each do |key|
+ bad_request!(key) unless params[key].present?
+ end
+ end
+
def attributes_for_keys(keys)
attrs = {}
keys.each do |key|
@@ -55,6 +93,12 @@ module Gitlab
render_api_error!('403 Forbidden', 403)
end
+ def bad_request!(attribute)
+ message = ["400 (Bad request)"]
+ message << "\"" + attribute.to_s + "\" not given"
+ render_api_error!(message.join(' '), 400)
+ end
+
def not_found!(resource = nil)
message = ["404"]
message << resource if resource
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 3e5e3a478ba..79f8eb3a543 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -1,23 +1,45 @@
-module Gitlab
+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
#
+ # Params:
+ # key_id - SSH Key id
+ # project - project path with namespace
+ # action - git action (git-upload-pack or git-receive-pack)
+ # ref - branch name
+ #
get "/allowed" do
+ # Check for *.wiki repositories.
+ # Strip out the .wiki from the pathname before finding the
+ # project. This applies the correct project permissions to
+ # 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(params[:project])
+ project = Project.find_with_namespace(project_path)
git_cmd = params[:action]
+ return false unless project
- if key.is_deploy_key
- project == key.project && git_cmd == 'git-upload-pack'
+
+ if key.is_a? DeployKey
+ key.projects.include?(project) && DOWNLOAD_COMMANDS.include?(git_cmd)
else
user = key.user
+
+ return false if user.blocked?
+
action = case git_cmd
- when 'git-upload-pack'
+ when *DOWNLOAD_COMMANDS
then :download_code
- when 'git-receive-pack'
+ when *PUSH_COMMANDS
then
if project.protected_branch?(params[:ref])
:push_code_to_protected_branches
@@ -35,12 +57,14 @@ module Gitlab
#
get "/discover" do
key = Key.find(params[:key_id])
- present key.user, with: Entities::User
+ present key.user, with: Entities::UserSafe
end
get "/check" do
{
- api_version: '3'
+ api_version: API.version,
+ gitlab_version: Gitlab::VERSION,
+ gitlab_rev: Gitlab::REVISION,
}
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 4d832fbe593..a15203d1563 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -1,7 +1,8 @@
-module Gitlab
+module API
# Issues API
class Issues < Grape::API
before { authenticate! }
+ before { Thread.current[:current_user] = current_user }
resource :issues do
# Get currently authenticated user's issues
@@ -48,6 +49,7 @@ module Gitlab
# Example Request:
# POST /projects/:id/issues
post ":id/issues" do
+ required_attributes! [:title]
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
attrs[:label_list] = params[:labels] if params[:labels].present?
@issue = user_project.issues.new attrs
@@ -69,16 +71,16 @@ module Gitlab
# assignee_id (optional) - The ID of a user to assign issue
# milestone_id (optional) - The ID of a milestone to assign issue
# labels (optional) - The labels of an issue
- # closed (optional) - The state of an issue (0 = false, 1 = true)
+ # state_event (optional) - The state event of an issue (close|reopen)
# Example Request:
# PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id])
authorize! :modify_issue, @issue
- attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :closed]
+ attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
attrs[:label_list] = params[:labels] if params[:labels].present?
- IssueObserver.current_user = current_user
+
if @issue.update_attributes attrs
present @issue, with: Entities::Issue
else
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 470cd1e1c2d..d690f1d07e7 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -1,9 +1,28 @@
-module Gitlab
+module API
# MergeRequest API
class MergeRequests < Grape::API
before { authenticate! }
+ before { Thread.current[:current_user] = current_user }
resource :projects do
+ helpers do
+ def handle_merge_request_errors!(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ elsif errors[:branch_conflict].any?
+ error!(errors[:branch_conflict], 422)
+ end
+ not_found!
+ end
+
+ def not_fork?(target_project_id, user_project)
+ target_project_id.nil? || target_project_id == user_project.id.to_s
+ end
+
+ def target_matches_fork(target_project_id,user_project)
+ user_project.forked? && user_project.forked_from_project.id.to_s == target_project_id
+ end
+ end
# List merge requests
#
@@ -40,9 +59,10 @@ module Gitlab
#
# Parameters:
#
- # id (required) - The ID of a project
+ # id (required) - The ID of a project - this will be the source of the merge request
# source_branch (required) - The source branch
# target_branch (required) - The target branch
+ # 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
#
@@ -51,16 +71,27 @@ module Gitlab
#
post ":id/merge_requests" do
authorize! :write_merge_request, user_project
-
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title]
+ required_attributes! [:source_branch, :target_branch, :title]
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id]
merge_request = user_project.merge_requests.new(attrs)
merge_request.author = current_user
+ merge_request.source_project = user_project
+ target_project_id = attrs[:target_project_id]
+ if not_fork?(target_project_id, user_project)
+ merge_request.target_project = user_project
+ else
+ if target_matches_fork(target_project_id,user_project)
+ merge_request.target_project = Project.find_by_id(attrs[:target_project_id])
+ else
+ render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400)
+ end
+ end
if merge_request.save
merge_request.reload_code
present merge_request, with: Entities::MergeRequest
else
- not_found!
+ handle_merge_request_errors! merge_request.errors
end
end
@@ -73,12 +104,12 @@ module Gitlab
# target_branch - The target branch
# assignee_id - Assignee user ID
# title - Title of MR
- # closed - Status of MR. true - closed
+ # state_event - Status of MR. (close|reopen|merge)
# Example:
# PUT /projects/:id/merge_request/:merge_request_id
#
put ":id/merge_request/:merge_request_id" do
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed]
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :modify_merge_request, merge_request
@@ -88,7 +119,7 @@ module Gitlab
merge_request.mark_as_unchecked
present merge_request, with: Entities::MergeRequest
else
- not_found!
+ handle_merge_request_errors! merge_request.errors
end
end
@@ -102,6 +133,8 @@ module Gitlab
# POST /projects/:id/merge_request/:merge_request_id/comments
#
post ":id/merge_request/:merge_request_id/comments" do
+ required_attributes! [:note]
+
merge_request = user_project.merge_requests.find(params[:merge_request_id])
note = merge_request.notes.new(note: params[:note], project_id: user_project.id)
note.author = current_user
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index 6aca9d01b09..aee12e7dc40 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module API
# Milestones API
class Milestones < Grape::API
before { authenticate! }
@@ -41,6 +41,7 @@ module Gitlab
# POST /projects/:id/milestones
post ":id/milestones" do
authorize! :admin_milestone, user_project
+ required_attributes! [:title]
attrs = attributes_for_keys [:title, :description, :due_date]
@milestone = user_project.milestones.new attrs
@@ -59,14 +60,14 @@ module Gitlab
# title (optional) - The title of a milestone
# description (optional) - The description of a milestone
# due_date (optional) - The due date of a milestone
- # closed (optional) - The status of the milestone
+ # state_event (optional) - The state event of the milestone (close|activate)
# Example Request:
# PUT /projects/:id/milestones/:milestone_id
put ":id/milestones/:milestone_id" do
authorize! :admin_milestone, user_project
@milestone = user_project.milestones.find(params[:milestone_id])
- attrs = attributes_for_keys [:title, :description, :due_date, :closed]
+ attrs = attributes_for_keys [:title, :description, :due_date, :state_event]
if @milestone.update_attributes attrs
present @milestone, with: Entities::Milestone
else
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 70344d6e381..cb2bc764476 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module API
# Notes API
class Notes < Grape::API
before { authenticate! }
@@ -14,6 +14,10 @@ module Gitlab
# GET /projects/:id/notes
get ":id/notes" do
@notes = user_project.notes.common
+
+ # Get recent notes if recent = true
+ @notes = @notes.order('id DESC') if params[:recent]
+
present paginate(@notes), with: Entities::Note
end
@@ -37,12 +41,16 @@ module Gitlab
# Example Request:
# POST /projects/:id/notes
post ":id/notes" do
+ required_attributes! [:body]
+
@note = user_project.notes.new(note: params[:body])
@note.author = current_user
if @note.save
present @note, with: Entities::Note
else
+ # :note is exposed as :body, but :note is set on error
+ bad_request!(:note) if @note.errors[:note].any?
not_found!
end
end
@@ -89,6 +97,8 @@ module Gitlab
# POST /projects/:id/issues/:noteable_id/notes
# POST /projects/:id/snippets/:noteable_id/notes
post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do
+ required_attributes! [:body]
+
@noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"])
@note = @noteable.notes.new(note: params[:body])
@note.author = current_user
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
new file mode 100644
index 00000000000..28501256795
--- /dev/null
+++ b/lib/api/project_hooks.rb
@@ -0,0 +1,108 @@
+module API
+ # Projects API
+ class ProjectHooks < 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 project hooks
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/hooks
+ get ":id/hooks" do
+ authorize! :admin_project, user_project
+ @hooks = paginate user_project.hooks
+ present @hooks, with: Entities::Hook
+ end
+
+ # Get a project hook
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of a project hook
+ # Example Request:
+ # GET /projects/:id/hooks/:hook_id
+ get ":id/hooks/:hook_id" do
+ authorize! :admin_project, user_project
+ @hook = user_project.hooks.find(params[:hook_id])
+ present @hook, with: Entities::Hook
+ end
+
+
+ # Add hook to project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # url (required) - The hook URL
+ # Example Request:
+ # POST /projects/:id/hooks
+ post ":id/hooks" do
+ authorize! :admin_project, user_project
+ required_attributes! [:url]
+
+ @hook = user_project.hooks.new({"url" => params[:url]})
+ if @hook.save
+ present @hook, with: Entities::Hook
+ else
+ if @hook.errors[:url].present?
+ error!("Invalid url given", 422)
+ end
+ not_found!
+ end
+ end
+
+ # Update an existing project hook
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of a project hook
+ # url (required) - The hook URL
+ # Example Request:
+ # PUT /projects/:id/hooks/:hook_id
+ put ":id/hooks/:hook_id" do
+ @hook = user_project.hooks.find(params[:hook_id])
+ authorize! :admin_project, user_project
+ required_attributes! [:url]
+
+ attrs = attributes_for_keys [:url]
+ if @hook.update_attributes attrs
+ present @hook, with: Entities::Hook
+ else
+ if @hook.errors[:url].present?
+ error!("Invalid url given", 422)
+ end
+ not_found!
+ end
+ end
+
+ # Deletes project hook. This is an idempotent function.
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of hook to delete
+ # Example Request:
+ # DELETE /projects/:id/hooks/:hook_id
+ delete ":id/hooks/:hook_id" do
+ authorize! :admin_project, user_project
+ required_attributes! [:hook_id]
+
+ begin
+ @hook = ProjectHook.find(params[:hook_id])
+ @hook.destroy
+ rescue
+ # ProjectHook can raise Error if hook_id not found
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
new file mode 100644
index 00000000000..bee6544ea3d
--- /dev/null
+++ b/lib/api/project_snippets.rb
@@ -0,0 +1,123 @@
+module API
+ # Projects API
+ class ProjectSnippets < 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 snippets
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/snippets
+ get ":id/snippets" do
+ present paginate(user_project.snippets), with: Entities::ProjectSnippet
+ end
+
+ # Get a project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # GET /projects/:id/snippets/:snippet_id
+ get ":id/snippets/:snippet_id" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ present @snippet, with: Entities::ProjectSnippet
+ end
+
+ # Create a new project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # title (required) - The title of a snippet
+ # file_name (required) - The name of a snippet file
+ # lifetime (optional) - The expiration date of a snippet
+ # code (required) - The content of a snippet
+ # Example Request:
+ # POST /projects/:id/snippets
+ post ":id/snippets" do
+ authorize! :write_project_snippet, user_project
+ required_attributes! [:title, :file_name, :code]
+
+ attrs = attributes_for_keys [:title, :file_name]
+ attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
+ attrs[:content] = params[:code] if params[:code].present?
+ @snippet = user_project.snippets.new attrs
+ @snippet.author = current_user
+
+ if @snippet.save
+ present @snippet, with: Entities::ProjectSnippet
+ else
+ not_found!
+ end
+ end
+
+ # Update an existing project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # title (optional) - The title of a snippet
+ # file_name (optional) - The name of a snippet file
+ # lifetime (optional) - The expiration date of a snippet
+ # code (optional) - The content of a snippet
+ # Example Request:
+ # PUT /projects/:id/snippets/:snippet_id
+ put ":id/snippets/:snippet_id" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ authorize! :modify_project_snippet, @snippet
+
+ attrs = attributes_for_keys [:title, :file_name]
+ attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
+ attrs[:content] = params[:code] if params[:code].present?
+
+ if @snippet.update_attributes attrs
+ present @snippet, with: Entities::ProjectSnippet
+ else
+ not_found!
+ end
+ end
+
+ # Delete a project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # DELETE /projects/:id/snippets/:snippet_id
+ delete ":id/snippets/:snippet_id" do
+ begin
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ authorize! :modify_project_snippet, @snippet
+ @snippet.destroy
+ rescue
+ end
+ end
+
+ # Get a raw project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # GET /projects/:id/snippets/:snippet_id/raw
+ get ":id/snippets/:snippet_id/raw" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+
+ env['api.format'] = :txt
+ content_type 'text/plain'
+ present @snippet.content
+ end
+ end
+ end
+end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index d416121a78a..cf357b23c40 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -1,9 +1,18 @@
-module Gitlab
+module API
# Projects API
class Projects < 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 projects list for authenticated user
#
# Example Request:
@@ -13,6 +22,15 @@ module Gitlab
present @projects, with: Entities::Project
end
+ # Get an owned projects list for authenticated user
+ #
+ # Example Request:
+ # GET /projects/owned
+ get '/owned' do
+ @projects = paginate current_user.owned_projects
+ present @projects, with: Entities::Project
+ end
+
# Get a single project
#
# Parameters:
@@ -23,32 +41,128 @@ module Gitlab
present user_project, with: Entities::Project
end
+ # Get a single project events
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id
+ get ":id/events" do
+ limit = (params[:per_page] || 20).to_i
+ offset = (params[:page] || 0).to_i * limit
+ events = user_project.events.recent.limit(limit).offset(offset)
+
+ present events, with: Entities::Event
+ end
+
# Create new project
#
# Parameters:
# name (required) - name for new project
# description (optional) - short project description
# default_branch (optional) - 'master' by default
- # issues_enabled (optional) - enabled by default
- # wall_enabled (optional) - enabled by default
- # merge_requests_enabled (optional) - enabled by default
- # wiki_enabled (optional) - enabled by default
+ # issues_enabled (optional)
+ # wall_enabled (optional)
+ # merge_requests_enabled (optional)
+ # wiki_enabled (optional)
+ # snippets_enabled (optional)
+ # namespace_id (optional) - defaults to user namespace
+ # public (optional) - false by default
# Example Request
# POST /projects
post do
+ required_attributes! [:name]
attrs = attributes_for_keys [:name,
- :description,
- :default_branch,
- :issues_enabled,
- :wall_enabled,
- :merge_requests_enabled,
- :wiki_enabled]
+ :path,
+ :description,
+ :default_branch,
+ :issues_enabled,
+ :wall_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :snippets_enabled,
+ :namespace_id,
+ :public]
@project = ::Projects::CreateContext.new(current_user, attrs).execute
if @project.saved?
present @project, with: Entities::Project
else
+ if @project.errors[:limit_reached].present?
+ error!(@project.errors[:limit_reached], 403)
+ end
+ not_found!
+ end
+ end
+
+ # Create new project for a specified user. Only available to admin users.
+ #
+ # Parameters:
+ # user_id (required) - The ID of a user
+ # name (required) - name for new project
+ # description (optional) - short project description
+ # default_branch (optional) - 'master' by default
+ # issues_enabled (optional)
+ # wall_enabled (optional)
+ # merge_requests_enabled (optional)
+ # wiki_enabled (optional)
+ # snippets_enabled (optional)
+ # public (optional)
+ # Example Request
+ # POST /projects/user/:user_id
+ post "user/:user_id" do
+ authenticated_as_admin!
+ user = User.find(params[:user_id])
+ attrs = attributes_for_keys [:name,
+ :description,
+ :default_branch,
+ :issues_enabled,
+ :wall_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :snippets_enabled,
+ :public]
+ @project = ::Projects::CreateContext.new(user, attrs).execute
+ if @project.saved?
+ present @project, with: Entities::Project
+ else
+ not_found!
+ end
+ end
+
+
+ # Mark this project as forked from another
+ #
+ # Parameters:
+ # id: (required) - The ID of the project being marked as a fork
+ # forked_from_id: (required) - The ID of the project it was forked from
+ # Example Request:
+ # POST /projects/:id/fork/:forked_from_id
+ post ":id/fork/:forked_from_id" do
+ authenticated_as_admin!
+ forked_from_project = find_project(params[:forked_from_id])
+ unless forked_from_project.nil?
+ if user_project.forked_from_project.nil?
+ user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
+ else
+ render_api_error!("Project already forked", 409)
+ end
+ else
not_found!
end
+
+ end
+
+ # Remove a forked_from relationship
+ #
+ # Parameters:
+ # id: (required) - The ID of the project being marked as a fork
+ # Example Request:
+ # DELETE /projects/:id/fork
+ delete ":id/fork" do
+ authenticated_as_admin!
+ unless user_project.forked_project_link.nil?
+ user_project.forked_project_link.destroy
+ end
end
# Get a project team members
@@ -89,16 +203,22 @@ module Gitlab
# POST /projects/:id/members
post ":id/members" do
authorize! :admin_project, user_project
- users_project = user_project.users_projects.new(
- user_id: params[:user_id],
- project_access: params[:access_level]
- )
+ 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 users_project.save
- @member = users_project.user
+ if team_member.save
+ @member = team_member.user
present @member, with: Entities::ProjectMember, project: user_project
else
- not_found!
+ handle_project_member_errors team_member.errors
end
end
@@ -112,13 +232,16 @@ module Gitlab
# PUT /projects/:id/members/:user_id
put ":id/members/:user_id" do
authorize! :admin_project, user_project
- users_project = user_project.users_projects.find_by_user_id params[:user_id]
+ 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 users_project.update_attributes(project_access: params[:access_level])
- @member = users_project.user
+ if team_member.update_attributes(project_access: params[:access_level])
+ @member = team_member.user
present @member, with: Entities::ProjectMember, project: user_project
else
- not_found!
+ handle_project_member_errors team_member.errors
end
end
@@ -131,297 +254,27 @@ module Gitlab
# DELETE /projects/:id/members/:user_id
delete ":id/members/:user_id" do
authorize! :admin_project, user_project
- users_project = user_project.users_projects.find_by_user_id params[:user_id]
- users_project.destroy
- end
-
- # Get project hooks
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/hooks
- get ":id/hooks" do
- authorize! :admin_project, user_project
- @hooks = paginate user_project.hooks
- present @hooks, with: Entities::Hook
- end
-
- # Get a project hook
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of a project hook
- # Example Request:
- # GET /projects/:id/hooks/:hook_id
- get ":id/hooks/:hook_id" do
- @hook = user_project.hooks.find(params[:hook_id])
- present @hook, with: Entities::Hook
- end
-
-
- # Add hook to project
- #
- # Parameters:
- # id (required) - The ID of a project
- # url (required) - The hook URL
- # Example Request:
- # POST /projects/:id/hooks
- post ":id/hooks" do
- authorize! :admin_project, user_project
- @hook = user_project.hooks.new({"url" => params[:url]})
- if @hook.save
- present @hook, with: Entities::Hook
+ team_member = user_project.users_projects.find_by_user_id(params[:user_id])
+ unless team_member.nil?
+ team_member.destroy
else
- error!({'message' => '404 Not found'}, 404)
+ {message: "Access revoked", id: params[:user_id].to_i}
end
end
- # Update an existing project hook
+ # search for projects current_user has access to
#
# Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of a project hook
- # url (required) - The hook URL
+ # 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:
- # PUT /projects/:id/hooks/:hook_id
- put ":id/hooks/:hook_id" do
- @hook = user_project.hooks.find(params[:hook_id])
- authorize! :admin_project, user_project
-
- attrs = attributes_for_keys [:url]
-
- if @hook.update_attributes attrs
- present @hook, with: Entities::Hook
- else
- not_found!
- end
+ # GET /projects/search/:query
+ get "/search/:query" do
+ ids = current_user.authorized_projects.map(&:id)
+ projects = Project.where("(id in (?) OR public = true) AND (name LIKE (?))", ids, "%#{params[:query]}%")
+ present paginate(projects), with: Entities::Project
end
-
- # Delete project hook
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of hook to delete
- # Example Request:
- # DELETE /projects/:id/hooks
- delete ":id/hooks" do
- authorize! :admin_project, user_project
- @hook = user_project.hooks.find(params[:hook_id])
- @hook.destroy
- end
-
- # Get a project repository branches
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/repository/branches
- get ":id/repository/branches" do
- present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project
- end
-
- # Get a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # GET /projects/:id/repository/branches/:branch
- get ":id/repository/branches/:branch" do
- @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
- not_found!("Branch does not exist") if @branch.nil?
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
- # Protect a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # PUT /projects/:id/repository/branches/:branch/protect
- put ":id/repository/branches/:branch/protect" do
- @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
- protected = user_project.protected_branches.find_by_name(@branch.name)
-
- unless protected
- user_project.protected_branches.create(:name => @branch.name)
- end
-
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
- # Unprotect a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # PUT /projects/:id/repository/branches/:branch/unprotect
- put ":id/repository/branches/:branch/unprotect" do
- @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
- protected = user_project.protected_branches.find_by_name(@branch.name)
-
- if protected
- protected.destroy
- end
-
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
- # Get a project repository tags
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/repository/tags
- get ":id/repository/tags" do
- present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject
- 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
- # Example Request:
- # GET /projects/:id/repository/commits
- get ":id/repository/commits" do
- authorize! :download_code, user_project
-
- page = params[:page] || 0
- per_page = params[:per_page] || 20
- ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
-
- commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
- present CommitDecorator.decorate(commits), with: Entities::RepoCommit
- end
-
- # Get a project snippets
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/snippets
- get ":id/snippets" do
- present paginate(user_project.snippets), with: Entities::ProjectSnippet
- end
-
- # Get a project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # GET /projects/:id/snippets/:snippet_id
- get ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- present @snippet, with: Entities::ProjectSnippet
- end
-
- # Create a new project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # title (required) - The title of a snippet
- # file_name (required) - The name of a snippet file
- # lifetime (optional) - The expiration date of a snippet
- # code (required) - The content of a snippet
- # Example Request:
- # POST /projects/:id/snippets
- post ":id/snippets" do
- authorize! :write_snippet, user_project
-
- attrs = attributes_for_keys [:title, :file_name]
- attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
- attrs[:content] = params[:code] if params[:code].present?
- @snippet = user_project.snippets.new attrs
- @snippet.author = current_user
-
- if @snippet.save
- present @snippet, with: Entities::ProjectSnippet
- else
- not_found!
- end
- end
-
- # Update an existing project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # title (optional) - The title of a snippet
- # file_name (optional) - The name of a snippet file
- # lifetime (optional) - The expiration date of a snippet
- # code (optional) - The content of a snippet
- # Example Request:
- # PUT /projects/:id/snippets/:snippet_id
- put ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- authorize! :modify_snippet, @snippet
-
- attrs = attributes_for_keys [:title, :file_name]
- attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
- attrs[:content] = params[:code] if params[:code].present?
-
- if @snippet.update_attributes attrs
- present @snippet, with: Entities::ProjectSnippet
- else
- not_found!
- end
- end
-
- # Delete a project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # DELETE /projects/:id/snippets/:snippet_id
- delete ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- authorize! :modify_snippet, @snippet
-
- @snippet.destroy
- end
-
- # Get a raw project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # GET /projects/:id/snippets/:snippet_id/raw
- get ":id/snippets/:snippet_id/raw" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- content_type 'text/plain'
- present @snippet.content
- end
-
- # Get a raw file contents
- #
- # Parameters:
- # id (required) - The ID of a project
- # sha (required) - The commit or branch name
- # filepath (required) - The path to the file to display
- # Example Request:
- # GET /projects/:id/repository/commits/:sha/blob
- get ":id/repository/commits/:sha/blob" do
- authorize! :download_code, user_project
-
- ref = params[:sha]
-
- commit = user_project.repository.commit ref
- not_found! "Commit" unless commit
-
- tree = Tree.new commit.tree, ref, params[:filepath]
- not_found! "File" unless tree.try(:tree)
-
- content_type tree.mime_type
- present tree.data
- end
-
end
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
new file mode 100644
index 00000000000..fef32d3a2fe
--- /dev/null
+++ b/lib/api/repositories.rb
@@ -0,0 +1,190 @@
+module API
+ # Projects API
+ class Repositories < 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 repository branches
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/repository/branches
+ get ":id/repository/branches" do
+ present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project
+ end
+
+ # Get a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # GET /projects/:id/repository/branches/:branch
+ get ":id/repository/branches/:branch" do
+ @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
+ not_found!("Branch does not exist") if @branch.nil?
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Protect a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # PUT /projects/:id/repository/branches/:branch/protect
+ put ":id/repository/branches/:branch/protect" do
+ @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
+ not_found! unless @branch
+ protected = user_project.protected_branches.find_by_name(@branch.name)
+
+ unless protected
+ user_project.protected_branches.create(name: @branch.name)
+ end
+
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Unprotect a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # PUT /projects/:id/repository/branches/:branch/unprotect
+ put ":id/repository/branches/:branch/unprotect" do
+ @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
+ not_found! unless @branch
+ protected = user_project.protected_branches.find_by_name(@branch.name)
+
+ if protected
+ protected.destroy
+ end
+
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Get a project repository tags
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/repository/tags
+ get ":id/repository/tags" do
+ present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject
+ 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
+ authorize! :download_code, user_project
+
+ 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
+ authorize! :download_code, user_project
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! "Commit" unless commit
+ present commit, with: Entities::RepoCommit
+ 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
+ authorize! :download_code, user_project
+ sha = params[:sha]
+ result = CommitLoadContext.new(user_project, current_user, {id: sha}).execute
+ not_found! "Commit" unless result[:commit]
+ result[:commit].diffs
+ end
+
+ # Get a project repository tree
+ #
+ # 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/tree
+ get ":id/repository/tree" do
+ authorize! :download_code, user_project
+
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ path = params[:path] || nil
+
+ commit = user_project.repository.commit(ref)
+ tree = Tree.new(user_project.repository, commit.id, ref, path)
+
+ trees = []
+
+ %w(trees blobs submodules).each do |type|
+ trees += tree.send(type).map { |t| { name: t.name, type: type.singularize, mode: t.mode, id: t.id } }
+ end
+
+ trees
+ end
+
+ # Get a raw file contents
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit or branch name
+ # filepath (required) - The path to the file to display
+ # Example Request:
+ # GET /projects/:id/repository/blobs/:sha
+ get [ ":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob" ] do
+ authorize! :download_code, user_project
+ required_attributes! [:filepath]
+
+ ref = params[:sha]
+
+ repo = user_project.repository
+
+ commit = repo.commit(ref)
+ not_found! "Commit" unless commit
+
+ blob = Gitlab::Git::Blob.new(repo, commit.id, ref, params[:filepath])
+ not_found! "File" unless blob.exists?
+
+ env['api.format'] = :txt
+
+ content_type blob.mime_type
+ present blob.data
+ end
+ end
+ end
+end
+
diff --git a/lib/api/session.rb b/lib/api/session.rb
index b4050160ae4..cc646895914 100644
--- a/lib/api/session.rb
+++ b/lib/api/session.rb
@@ -1,20 +1,21 @@
-module Gitlab
+module API
# Users API
class Session < Grape::API
# Login to get token
#
+ # Parameters:
+ # login (*required) - user login
+ # email (*required) - user email
+ # password (required) - user password
+ #
# Example Request:
# POST /session
post "/session" do
- resource = User.find_for_database_authentication(email: params[:email])
-
- return unauthorized! unless resource
+ auth = Gitlab::Auth.new
+ user = auth.find(params[:email] || params[:login], params[:password])
- if resource.valid_password?(params[:password])
- present resource, with: Entities::UserLogin
- else
- unauthorized!
- end
+ return unauthorized! unless user
+ present user, with: Entities::UserLogin
end
end
end
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
new file mode 100644
index 00000000000..3e239c5afe7
--- /dev/null
+++ b/lib/api/system_hooks.rb
@@ -0,0 +1,70 @@
+module API
+ # Hooks API
+ class SystemHooks < Grape::API
+ before {
+ authenticate!
+ authenticated_as_admin!
+ }
+
+ resource :hooks do
+ # Get the list of system hooks
+ #
+ # Example Request:
+ # GET /hooks
+ get do
+ @hooks = SystemHook.all
+ present @hooks, with: Entities::Hook
+ end
+
+ # Create new system hook
+ #
+ # Parameters:
+ # url (required) - url for system hook
+ # Example Request
+ # POST /hooks
+ post do
+ attrs = attributes_for_keys [:url]
+ required_attributes! [:url]
+ @hook = SystemHook.new attrs
+ if @hook.save
+ present @hook, with: Entities::Hook
+ else
+ not_found!
+ end
+ end
+
+ # Test a hook
+ #
+ # Example Request
+ # GET /hooks/:id
+ get ":id" do
+ @hook = SystemHook.find(params[:id])
+ data = {
+ event_name: "project_create",
+ name: "Ruby",
+ path: "ruby",
+ project_id: 1,
+ owner_name: "Someone",
+ owner_email: "example@gitlabhq.com"
+ }
+ @hook.execute(data)
+ data
+ end
+
+ # Delete a hook. This is an idempotent function.
+ #
+ # Parameters:
+ # id (required) - ID of the hook
+ # Example Request:
+ # DELETE /hooks/:id
+ delete ":id" do
+ begin
+ @hook = SystemHook.find(params[:id])
+ @hook.destroy
+ rescue
+ # SystemHook raises an Error if no hook with id found
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 7ea90c75e9e..00dc2311ffd 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module API
# Users API
class Users < Grape::API
before { authenticate! }
@@ -9,7 +9,10 @@ module Gitlab
# Example Request:
# GET /users
get do
- @users = paginate User
+ @users = User.scoped
+ @users = @users.active if params[:active].present?
+ @users = @users.search(params[:search]) if params[:search].present?
+ @users = paginate @users
present @users, with: Entities::User
end
@@ -41,8 +44,9 @@ module Gitlab
# POST /users
post do
authenticated_as_admin!
+ required_attributes! [:email, :password, :name, :username]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio]
- user = User.new attrs, as: :admin
+ user = User.build_user(attrs, as: :admin)
if user.save
present user, with: Entities::User
else
@@ -59,7 +63,7 @@ module Gitlab
# skype - Skype ID
# linkedin - Linkedin
# twitter - Twitter account
- # projects_limit - Limit projects wich user can create
+ # projects_limit - Limit projects each user can create
# extern_uid - External authentication provider UID
# provider - External provider
# bio - Bio
@@ -67,16 +71,38 @@ module Gitlab
# PUT /users/:id
put ":id" do
authenticated_as_admin!
+
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio]
- user = User.find_by_id(params[:id])
+ user = User.find(params[:id])
+ not_found!("User not found") unless user
- if user && user.update_attributes(attrs)
+ if user.update_attributes(attrs)
present user, with: Entities::User
else
not_found!
end
end
+ # Add ssh key to a specified user. Only available to admin users.
+ #
+ # Parameters:
+ # id (required) - The ID of a user
+ # key (required) - New SSH Key
+ # title (required) - New SSH Key's title
+ # Example Request:
+ # POST /users/:id/keys
+ post ":id/keys" do
+ authenticated_as_admin!
+ user = User.find(params[:id])
+ attrs = attributes_for_keys [:title, :key]
+ key = user.keys.new attrs
+ if key.save
+ present key, with: Entities::SSHKey
+ else
+ not_found!
+ end
+ end
+
# Delete user. Available only for admin
#
# Example Request:
@@ -99,7 +125,7 @@ module Gitlab
# Example Request:
# GET /user
get do
- present @current_user, with: Entities::User
+ present @current_user, with: Entities::UserLogin
end
# Get currently authenticated user's keys
@@ -127,6 +153,8 @@ module Gitlab
# Example Request:
# POST /user/keys
post "keys" do
+ required_attributes! [:title, :key]
+
attrs = attributes_for_keys [:title, :key]
key = current_user.keys.new attrs
if key.save
@@ -136,15 +164,18 @@ module Gitlab
end
end
- # Delete existed ssh key of currently authenticated user
+ # Delete existing ssh key of currently authenticated user
#
# Parameters:
# id (required) - SSH Key ID
# Example Request:
# DELETE /user/keys/:id
delete "keys/:id" do
- key = current_user.keys.find params[:id]
- key.delete
+ begin
+ key = current_user.keys.find params[:id]
+ key.destroy
+ rescue
+ end
end
end
end
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
new file mode 100644
index 00000000000..c4fb2e2e159
--- /dev/null
+++ b/lib/backup/database.rb
@@ -0,0 +1,58 @@
+require 'yaml'
+
+module Backup
+ class Database
+ attr_reader :config, :db_dir
+
+ def initialize
+ @config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env]
+ @db_dir = File.join(Gitlab.config.backup.path, 'db')
+ FileUtils.mkdir_p(@db_dir) unless Dir.exists?(@db_dir)
+ end
+
+ def dump
+ case config["adapter"]
+ when /^mysql/ then
+ system("mysqldump #{mysql_args} #{config['database']} > #{db_file_name}")
+ when "postgresql" then
+ pg_env
+ system("pg_dump #{config['database']} > #{db_file_name}")
+ end
+ end
+
+ def restore
+ case config["adapter"]
+ when /^mysql/ then
+ system("mysql #{mysql_args} #{config['database']} < #{db_file_name}")
+ when "postgresql" then
+ pg_env
+ system("psql #{config['database']} -f #{db_file_name}")
+ end
+ end
+
+ protected
+
+ def db_file_name
+ File.join(db_dir, 'database.sql')
+ end
+
+ def mysql_args
+ args = {
+ 'host' => '--host',
+ 'port' => '--port',
+ 'socket' => '--socket',
+ 'username' => '--user',
+ 'encoding' => '--default-character-set',
+ 'password' => '--password'
+ }
+ args.map { |opt, arg| "#{arg}='#{config[opt]}'" if config[opt] }.compact.join(' ')
+ end
+
+ def pg_env
+ ENV['PGUSER'] = config["username"] if config["username"]
+ ENV['PGHOST'] = config["host"] if config["host"]
+ ENV['PGPORT'] = config["port"].to_s if config["port"]
+ ENV['PGPASSWORD'] = config["password"].to_s if config["password"]
+ end
+ end
+end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
new file mode 100644
index 00000000000..258a0fb2589
--- /dev/null
+++ b/lib/backup/manager.rb
@@ -0,0 +1,106 @@
+module Backup
+ class Manager
+ def pack
+ # saving additional informations
+ 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/,"")
+
+ Dir.chdir(Gitlab.config.backup.path)
+
+ File.open("#{Gitlab.config.backup.path}/backup_information.yml", "w+") do |file|
+ file << s.to_yaml.gsub(/^---\n/,'')
+ end
+
+ # create archive
+ print "Creating backup archive: #{s[:backup_created_at].to_i}_gitlab_backup.tar ... "
+ if Kernel.system("tar -cf #{s[:backup_created_at].to_i}_gitlab_backup.tar repositories/ db/ uploads/ backup_information.yml")
+ puts "done".green
+ else
+ puts "failed".red
+ end
+ end
+
+ def cleanup
+ print "Deleting tmp directories ... "
+ if Kernel.system("rm -rf repositories/ db/ uploads/ backup_information.yml")
+ puts "done".green
+ else
+ puts "failed".red
+ end
+ end
+
+ def remove_old
+ # delete backups
+ print "Deleting old backups ... "
+ keep_time = Gitlab.config.backup.keep_time.to_i
+ path = Gitlab.config.backup.path
+
+ if keep_time > 0
+ removed = 0
+ file_list = Dir.glob(Rails.root.join(path, "*_gitlab_backup.tar"))
+ file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ }
+ file_list.sort.each do |timestamp|
+ if Time.at(timestamp) < (Time.now - keep_time)
+ if system("rm #{timestamp}_gitlab_backup.tar")
+ removed += 1
+ end
+ end
+ end
+ puts "done. (#{removed} removed)".green
+ else
+ puts "skipping".yellow
+ end
+ end
+
+ def unpack
+ Dir.chdir(Gitlab.config.backup.path)
+
+ # check for existing backups in the backup dir
+ file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
+ puts "no backups found" if file_list.count == 0
+ if file_list.count > 1 && ENV["BACKUP"].nil?
+ puts "Found more than one backup, please specify which one you want to restore:"
+ puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup"
+ exit 1
+ end
+
+ tar_file = ENV["BACKUP"].nil? ? File.join("#{file_list.first}_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar")
+
+ unless File.exists?(tar_file)
+ puts "The specified backup doesn't exist!"
+ exit 1
+ end
+
+ print "Unpacking backup ... "
+ unless Kernel.system("tar -xf #{tar_file}")
+ puts "failed".red
+ exit 1
+ else
+ puts "done".green
+ end
+
+ 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)
+ end
+ end
+ end
+end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
new file mode 100644
index 00000000000..c5e3d049fd7
--- /dev/null
+++ b/lib/backup/repository.rb
@@ -0,0 +1,105 @@
+require 'yaml'
+
+module Backup
+ class Repository
+ attr_reader :repos_path
+
+ def dump
+ prepare
+
+ Project.find_each(batch_size: 1000) do |project|
+ print " * #{project.path_with_namespace} ... "
+
+ if project.empty_repo?
+ puts "[SKIPPED]".cyan
+ next
+ end
+
+ # Create namespace dir if missing
+ FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
+
+ if system("cd #{path_to_repo(project)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(project)} --all > /dev/null 2>&1")
+ puts "[DONE]".green
+ else
+ puts "[FAILED]".red
+ end
+
+ wiki = GollumWiki.new(project)
+
+ if File.exists?(path_to_repo(wiki))
+ print " * #{wiki.path_with_namespace} ... "
+ if system("cd #{path_to_repo(wiki)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(wiki)} --all > /dev/null 2>&1")
+ puts " [DONE]".green
+ else
+ puts " [FAILED]".red
+ end
+ end
+ end
+ end
+
+ def restore
+ if File.exists?(repos_path)
+ # Move repos dir to 'repositories.old' dir
+ bk_repos_path = File.join(repos_path, '..', 'repositories.old.' + Time.now.to_i.to_s)
+ FileUtils.mv(repos_path, bk_repos_path)
+ end
+
+ FileUtils.mkdir_p(repos_path)
+
+ Project.find_each(batch_size: 1000) do |project|
+ print "#{project.path_with_namespace} ... "
+
+ project.namespace.ensure_dir_exist if project.namespace
+
+ if system("git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)} > /dev/null 2>&1")
+ puts "[DONE]".green
+ else
+ puts "[FAILED]".red
+ end
+
+ wiki = GollumWiki.new(project)
+
+ if File.exists?(path_to_bundle(wiki))
+ print " * #{wiki.path_with_namespace} ... "
+ if system("git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)} > /dev/null 2>&1")
+ puts " [DONE]".green
+ else
+ puts " [FAILED]".red
+ end
+ end
+ 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}")
+ puts " [DONE]".green
+ else
+ puts " [FAILED]".red
+ end
+
+ end
+
+ protected
+
+ def path_to_repo(project)
+ File.join(repos_path, project.path_with_namespace + '.git')
+ end
+
+ def path_to_bundle(project)
+ File.join(backup_repos_path, project.path_with_namespace + ".bundle")
+ end
+
+ def repos_path
+ Gitlab.config.gitlab_shell.repos_path
+ end
+
+ def backup_repos_path
+ File.join(Gitlab.config.backup.path, "repositories")
+ end
+
+ def prepare
+ FileUtils.rm_rf(backup_repos_path)
+ FileUtils.mkdir_p(backup_repos_path)
+ end
+ end
+end
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
new file mode 100644
index 00000000000..462d3f1e274
--- /dev/null
+++ b/lib/backup/uploads.rb
@@ -0,0 +1,29 @@
+module Backup
+ class Uploads
+ attr_reader :app_uploads_dir, :backup_uploads_dir, :backup_dir
+
+ def initialize
+ @app_uploads_dir = Rails.root.join('public', 'uploads')
+ @backup_dir = Gitlab.config.backup.path
+ @backup_uploads_dir = File.join(Gitlab.config.backup.path, 'uploads')
+ end
+
+ # Copy uploads from public/uploads to backup/uploads
+ def dump
+ FileUtils.mkdir_p(backup_uploads_dir)
+ FileUtils.cp_r(app_uploads_dir, backup_dir)
+ end
+
+ def restore
+ backup_existing_uploads_dir
+
+ FileUtils.cp_r(backup_uploads_dir, app_uploads_dir)
+ end
+
+ def backup_existing_uploads_dir
+ if File.exists?(app_uploads_dir)
+ FileUtils.mv(app_uploads_dir, Rails.root.join('public', "uploads.#{Time.now.to_i}"))
+ end
+ end
+ end
+end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index fb595e18b24..53bc079296a 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -8,7 +8,7 @@ module ExtractsPath
included do
if respond_to?(:before_filter)
- before_filter :assign_ref_vars, only: [:show]
+ before_filter :assign_ref_vars
end
end
@@ -33,7 +33,7 @@ module ExtractsPath
# extract_ref("v2.0.0/README.md")
# # => ['v2.0.0', 'README.md']
#
- # extract_ref('/gitlab/vagrant/tree/master/app/models/project.rb')
+ # extract_ref('master/app/models/project.rb')
# # => ['master', 'app/models/project.rb']
#
# extract_ref('issues/1234/app/models/project.rb')
@@ -45,22 +45,12 @@ module ExtractsPath
#
# Returns an Array where the first value is the tree-ish and the second is the
# path
- def extract_ref(input)
+ def extract_ref(id)
pair = ['', '']
return pair unless @project
- # Remove relative_url_root from path
- input.gsub!(/^#{Gitlab.config.gitlab.relative_url_root}/, "")
- # Remove project, actions and all other staff from path
- input.gsub!(/^\/#{Regexp.escape(@project.path_with_namespace)}/, "")
- input.gsub!(/^\/(tree|commits|blame|blob|refs|graph)\//, "") # remove actions
- input.gsub!(/\?.*$/, "") # remove stamps suffix
- input.gsub!(/.atom$/, "") # remove rss feed
- input.gsub!(/.json$/, "") # remove json suffix
- input.gsub!(/\/edit$/, "") # remove edit route part
-
- if input.match(/^([[:alnum:]]{40})(.+)/)
+ if id.match(/^([[:alnum:]]{40})(.+)/)
# If the ref appears to be a SHA, we're done, just split the string
pair = $~.captures
else
@@ -68,7 +58,6 @@ module ExtractsPath
# branches and tags
# Append a trailing slash if we only get a ref and no file path
- id = input
id += '/' unless id.ends_with?('/')
valid_refs = @project.repository.ref_names
@@ -96,8 +85,8 @@ module ExtractsPath
# - @id - A string representing the joined ref and path
# - @ref - A string representing the ref (e.g., the branch, tag, or commit SHA)
# - @path - A string representing the filesystem path
- # - @commit - A CommitDecorator representing the commit from the given ref
- # - @tree - A TreeDecorator representing the tree at the given ref/path
+ # - @commit - A Commit representing the commit from the given ref
+ # - @tree - A Tree representing the tree at the given ref/path
#
# If the :id parameter appears to be requesting a specific response format,
# that will be handled as well.
@@ -105,28 +94,33 @@ module ExtractsPath
# Automatically renders `not_found!` if a valid tree path could not be
# resolved (e.g., when a user inserts an invalid path or ref).
def assign_ref_vars
- # Handle formats embedded in the id
- if params[:id].ends_with?('.atom')
- params[:id].gsub!(/\.atom$/, '')
- request.format = :atom
+ # assign allowed options
+ allowed_options = ["filter_ref", "extended_sha1"]
+ @options = params.select {|key, value| allowed_options.include?(key) && !value.blank? }
+ @options = HashWithIndifferentAccess.new(@options)
+
+ @id = get_id
+ @ref, @path = extract_ref(@id)
+ @repo = @project.repository
+ if @options[:extended_sha1].blank?
+ @commit = @repo.commit(@ref)
+ else
+ @commit = @repo.commit(@options[:extended_sha1])
end
+ @tree = Tree.new(@repo, @commit.id, @ref, @path)
+ @hex_path = Digest::SHA1.hexdigest(@path)
+ @logs_path = logs_file_project_ref_path(@project, @ref, @path)
- path = CGI::unescape(request.fullpath.dup)
-
- @ref, @path = extract_ref(path)
-
- @id = File.join(@ref, @path)
-
- # It is used "@project.repository.commits(@ref, @path, 1, 0)",
- # because "@project.repository.commit(@ref)" returns wrong commit when @ref is tag name.
- commits = @project.repository.commits(@ref, @path, 1, 0)
- @commit = CommitDecorator.decorate(commits.first)
+ raise InvalidPathError unless @tree.exists?
+ rescue RuntimeError, NoMethodError, InvalidPathError
+ not_found!
+ end
- @tree = Tree.new(@commit.tree, @ref, @path)
- @tree = TreeDecorator.new(@tree)
+ private
- raise InvalidPathError if @tree.invalid?
- rescue NoMethodError, InvalidPathError
- not_found!
+ def get_id
+ id = params[:id] || params[:ref]
+ id += "/" + params[:path] unless params[:path].blank?
+ id
end
end
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
new file mode 100644
index 00000000000..87f9cfab608
--- /dev/null
+++ b/lib/gitlab/access.rb
@@ -0,0 +1,52 @@
+# Gitlab::Access module
+#
+# Define allowed roles that can be used
+# in GitLab code to determine authorization level
+#
+module Gitlab
+ module Access
+ GUEST = 10
+ REPORTER = 20
+ DEVELOPER = 30
+ MASTER = 40
+ OWNER = 50
+
+ class << self
+ def values
+ options.values
+ end
+
+ def options
+ {
+ "Guest" => GUEST,
+ "Reporter" => REPORTER,
+ "Developer" => DEVELOPER,
+ "Master" => MASTER,
+ }
+ end
+
+ def options_with_owner
+ options.merge(
+ "Owner" => OWNER
+ )
+ end
+
+ def sym_options
+ {
+ guest: GUEST,
+ reporter: REPORTER,
+ developer: DEVELOPER,
+ master: MASTER,
+ }
+ end
+ end
+
+ def human_access
+ Gitlab::Access.options_with_owner.key(access_field)
+ end
+
+ def owner?
+ access_field == OWNER
+ end
+ end
+end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index d0e792befbb..0f196297477 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,72 +1,24 @@
module Gitlab
class Auth
- def find_for_ldap_auth(auth, signed_in_resource = nil)
- uid = auth.info.uid
- provider = auth.provider
- email = auth.info.email.downcase unless auth.info.email.nil?
- raise OmniAuth::Error, "LDAP accounts must provide an uid and email address" if uid.nil? or email.nil?
+ def find(login, password)
+ user = User.find_by_email(login) || User.find_by_username(login)
- if @user = User.find_by_extern_uid_and_provider(uid, provider)
- @user
- elsif @user = User.find_by_email(email)
- log.info "Updating legacy LDAP user #{email} with extern_uid => #{uid}"
- @user.update_attributes(:extern_uid => uid, :provider => provider)
- @user
- else
- create_from_omniauth(auth, true)
- end
- end
+ if user.nil? || user.ldap_user?
+ # Second chance - try LDAP authentication
+ return nil unless ldap_conf.enabled
- def create_from_omniauth(auth, ldap = false)
- provider = auth.provider
- uid = auth.info.uid || auth.uid
- uid = uid.to_s.force_encoding("utf-8")
- name = auth.info.name.to_s.force_encoding("utf-8")
- email = auth.info.email.to_s.downcase unless auth.info.email.nil?
-
- ldap_prefix = ldap ? '(LDAP) ' : ''
- raise OmniAuth::Error, "#{ldap_prefix}#{provider} does not provide an email"\
- " address" if auth.info.email.blank?
-
- log.info "#{ldap_prefix}Creating user from #{provider} login"\
- " {uid => #{uid}, name => #{name}, email => #{email}}"
- password = Devise.friendly_token[0, 8].downcase
- @user = User.new({
- extern_uid: uid,
- provider: provider,
- name: name,
- username: email.match(/^[^@]*/)[0],
- email: email,
- password: password,
- password_confirmation: password,
- projects_limit: Gitlab.config.gitlab.default_projects_limit,
- }, as: :admin)
- if Gitlab.config.omniauth['block_auto_created_users'] && !ldap
- @user.blocked = true
- end
- @user.save!
- @user
- end
-
- def find_or_new_for_omniauth(auth)
- provider, uid = auth.provider, auth.uid
- email = auth.info.email.downcase unless auth.info.email.nil?
-
- if @user = User.find_by_provider_and_extern_uid(provider, uid)
- @user
- elsif @user = User.find_by_email(email)
- @user.update_attributes(:extern_uid => uid, :provider => provider)
- @user
+ Gitlab::LDAP::User.authenticate(login, password)
else
- if Gitlab.config.omniauth['allow_single_sign_on']
- @user = create_from_omniauth(auth)
- @user
- end
+ user if user.valid_password?(password)
end
end
def log
Gitlab::AppLogger
end
+
+ def ldap_conf
+ @ldap_conf ||= Gitlab.config.ldap
+ end
end
end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index abbee6132d3..c522e0a413b 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -1,8 +1,11 @@
require_relative 'shell_env'
+require_relative 'grack_helpers'
module Grack
class Auth < Rack::Auth::Basic
- attr_accessor :user, :project
+ include Helpers
+
+ attr_accessor :user, :project, :ref, :env
def call(env)
@env = env
@@ -10,52 +13,73 @@ module Grack
@auth = Request.new(env)
# Need this patch due to the rails mount
- @env['PATH_INFO'] = @request.path
- @env['SCRIPT_NAME'] = ""
- return render_not_found unless project
- return unauthorized unless project.public || @auth.provided?
- return bad_request if @auth.provided? && !@auth.basic?
-
- if valid?
- if @auth.provided?
- @env['REMOTE_USER'] = @auth.username
- end
- return @app.call(env)
+ # Need this if under RELATIVE_URL_ROOT
+ unless Gitlab.config.gitlab.relative_url_root.empty?
+ # If website is mounted using relative_url_root need to remove it first
+ @env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root,'')
else
- unauthorized
+ @env['PATH_INFO'] = @request.path
end
+
+ @env['SCRIPT_NAME'] = ""
+
+ auth!
end
- def valid?
+ private
+
+ def auth!
+ return render_not_found unless project
+
if @auth.provided?
+ return bad_request unless @auth.basic?
+
# Authentication with username and password
login, password = @auth.credentials
- self.user = User.find_by_email(login) || User.find_by_username(login)
- return false unless user.try(:valid_password?, password)
- Gitlab::ShellEnv.set_env(user)
+ @user = authenticate_user(login, password)
+
+ 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?
+ @app.call(env)
+ else
+ unauthorized
+ end
+ end
+
+ def authorized_git_request?
# Git upload and receive
if @request.get?
- validate_get_request
+ authorize_request(@request.params['service'])
elsif @request.post?
- validate_post_request
+ authorize_request(File.basename(@request.path))
else
false
end
end
- def validate_get_request
- project.public || can?(user, :download_code, project)
+ def authenticate_user(login, password)
+ auth = Gitlab::Auth.new
+ auth.find(login, password)
end
- def validate_post_request
- if @request.path_info.end_with?('git-upload-pack')
+ def authorize_request(service)
+ case service
+ when 'git-upload-pack'
project.public || can?(user, :download_code, project)
- elsif @request.path_info.end_with?('git-receive-pack')
- action = if project.protected_branch?(current_ref)
+ when'git-receive-pack'
+ action = if project.protected_branch?(ref)
:push_code_to_protected_branches
else
:push_code
@@ -67,45 +91,24 @@ module Grack
end
end
- def can?(object, action, subject)
- abilities.allowed?(object, action, subject)
- end
-
- def current_ref
- if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
- input = Zlib::GzipReader.new(@request.body).read
- else
- input = @request.body.read
- end
- # Need to reset seek point
- @request.body.rewind
- /refs\/heads\/([\w\.-]+)/.match(input).to_a.last
- end
-
def project
- unless instance_variable_defined? :@project
- # Find project by PATH_INFO from env
- if m = /^\/([\w\.\/-]+)\.git/.match(@request.path_info).to_a
- @project = Project.find_with_namespace(m.last)
- end
- end
- return @project
+ @project ||= project_by_path(@request.path_info)
end
- PLAIN_TYPE = {"Content-Type" => "text/plain"}
-
- def render_not_found
- [404, PLAIN_TYPE, ["Not Found"]]
+ def ref
+ @ref ||= parse_ref
end
- protected
+ def parse_ref
+ input = if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
+ Zlib::GzipReader.new(@request.body).read
+ else
+ @request.body.read
+ end
- def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << Ability
- abilities
- end
+ # Need to reset seek point
+ @request.body.rewind
+ /refs\/heads\/([\/\w\.-]+)/n.match(input.force_encoding('ascii-8bit')).to_a.last
end
- end# Auth
-end# Grack
+ end
+end
diff --git a/lib/gitlab/backend/grack_helpers.rb b/lib/gitlab/backend/grack_helpers.rb
new file mode 100644
index 00000000000..5ac9e9f325b
--- /dev/null
+++ b/lib/gitlab/backend/grack_helpers.rb
@@ -0,0 +1,28 @@
+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 b7b92e86a87..c819ce56ac9 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -10,7 +10,7 @@ module Gitlab
# add_repository("gitlab/gitlab-ci")
#
def add_repository(name)
- system("/home/git/gitlab-shell/bin/gitlab-projects add-project #{name}.git")
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "add-project", "#{name}.git"
end
# Import repository
@@ -21,7 +21,43 @@ module Gitlab
# import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
def import_repository(name, url)
- system("/home/git/gitlab-shell/bin/gitlab-projects import-project #{name}.git #{url}")
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "import-project", "#{name}.git", url
+ end
+
+ # Move repository
+ #
+ # path - project path with namespace
+ # new_path - new project path with namespace
+ #
+ # Ex.
+ # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git")
+ #
+ def mv_repository(path, new_path)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git"
+ end
+
+ # Update HEAD for repository
+ #
+ # path - project path with namespace
+ # branch - repository branch name
+ #
+ # Ex.
+ # update_repository_head("gitlab/gitlab-ci", "3-1-stable")
+ #
+ def update_repository_head(path, branch)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "update-head", "#{path}.git", branch
+ end
+
+ # Fork repository to new namespace
+ #
+ # path - project path with namespace
+ # fork_namespace - namespace for forked project
+ #
+ # Ex.
+ # fork_repository("gitlab/gitlab-ci", "randx")
+ #
+ def fork_repository(path, fork_namespace)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace
end
# Remove repository from file system
@@ -32,7 +68,57 @@ module Gitlab
# remove_repository("gitlab/gitlab-ci")
#
def remove_repository(name)
- system("/home/git/gitlab-shell/bin/gitlab-projects rm-project #{name}.git")
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-project", "#{name}.git"
+ end
+
+ # Add repository branch from passed ref
+ #
+ # path - project path with namespace
+ # branch_name - new branch name
+ # ref - HEAD for new branch
+ #
+ # Ex.
+ # add_branch("gitlab/gitlab-ci", "4-0-stable", "master")
+ #
+ def add_branch(path, branch_name, ref)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref
+ end
+
+ # Remove repository branch
+ #
+ # path - project path with namespace
+ # branch_name - branch name to remove
+ #
+ # Ex.
+ # rm_branch("gitlab/gitlab-ci", "4-0-stable")
+ #
+ def rm_branch(path, branch_name)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name
+ end
+
+ # Add repository tag from passed ref
+ #
+ # path - project path with namespace
+ # tag_name - new tag name
+ # ref - HEAD for new tag
+ #
+ # Ex.
+ # add_tag("gitlab/gitlab-ci", "v4.0", "master")
+ #
+ def add_tag(path, tag_name, ref)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref
+ end
+
+ # Remove repository tag
+ #
+ # path - project path with namespace
+ # tag_name - tag name to remove
+ #
+ # Ex.
+ # rm_tag("gitlab/gitlab-ci", "v4.0")
+ #
+ def rm_tag(path, tag_name)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name
end
# Add new key to gitlab-shell
@@ -41,7 +127,7 @@ module Gitlab
# add_key("key-42", "sha-rsa ...")
#
def add_key(key_id, key_content)
- system("/home/git/gitlab-shell/bin/gitlab-keys add-key #{key_id} \"#{key_content}\"")
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "add-key", key_id, key_content
end
# Remove ssh key from gitlab shell
@@ -50,11 +136,84 @@ module Gitlab
# remove_key("key-342", "sha-rsa ...")
#
def remove_key(key_id, key_content)
- system("/home/git/gitlab-shell/bin/gitlab-keys rm-key #{key_id} \"#{key_content}\"")
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "rm-key", key_id, key_content
+ end
+
+ # Remove all ssh keys from gitlab shell
+ #
+ # Ex.
+ # remove_all_keys
+ #
+ def remove_all_keys
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "clear"
+ end
+
+ # Add empty directory for storing repositories
+ #
+ # Ex.
+ # add_namespace("gitlab")
+ #
+ def add_namespace(name)
+ FileUtils.mkdir(full_path(name), mode: 0770) unless exists?(name)
+ end
+
+ # Remove directory from repositories storage
+ # Every repository inside this directory will be removed too
+ #
+ # Ex.
+ # rm_namespace("gitlab")
+ #
+ def rm_namespace(name)
+ FileUtils.rm_r(full_path(name), force: true)
+ end
+
+ # Move namespace directory inside repositories storage
+ #
+ # Ex.
+ # mv_namespace("gitlab", "gitlabhq")
+ #
+ def mv_namespace(old_name, new_name)
+ return false if exists?(new_name) || !exists?(old_name)
+
+ FileUtils.mv(full_path(old_name), full_path(new_name))
+ end
+
+ # Remove GitLab Satellites for provided path (namespace or repo dir)
+ #
+ # Ex.
+ # rm_satellites("gitlab")
+ #
+ # rm_satellites("gitlab/gitlab-ci.git")
+ #
+ def rm_satellites(path)
+ raise ArgumentError.new("Path can't be blank") if path.blank?
+
+ satellites_path = File.join(Gitlab.config.satellites.path, path)
+ FileUtils.rm_r(satellites_path, force: true)
end
def url_to_repo path
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
end
+
+ protected
+
+ def gitlab_shell_user_home
+ File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
+ end
+
+ def repos_path
+ Gitlab.config.gitlab_shell.repos_path
+ end
+
+ def full_path(dir_name)
+ raise ArgumentError.new("Directory name can't be blank") if dir_name.blank?
+
+ File.join(repos_path, dir_name)
+ end
+
+ def exists?(dir_name)
+ File.exists?(full_path(dir_name))
+ end
end
end
diff --git a/lib/gitlab/backend/shell_adapter.rb b/lib/gitlab/backend/shell_adapter.rb
new file mode 100644
index 00000000000..f247f4593d7
--- /dev/null
+++ b/lib/gitlab/backend/shell_adapter.rb
@@ -0,0 +1,12 @@
+# == GitLab Shell mixin
+#
+# Provide a shortcut to Gitlab::Shell instance by gitlab_shell
+#
+module Gitlab
+ module ShellAdapter
+ def gitlab_shell
+ Gitlab::Shell.new
+ end
+ end
+end
+
diff --git a/lib/gitlab/backend/shell_env.rb b/lib/gitlab/backend/shell_env.rb
index 15721875093..044afb27f3f 100644
--- a/lib/gitlab/backend/shell_env.rb
+++ b/lib/gitlab/backend/shell_env.rb
@@ -1,6 +1,6 @@
module Gitlab
# This module provide 2 methods
- # to set specific ENV variabled for GitLab Shell
+ # to set specific ENV variables for GitLab Shell
module ShellEnv
extend self
diff --git a/lib/gitlab/blacklist.rb b/lib/gitlab/blacklist.rb
new file mode 100644
index 00000000000..2f9091e07df
--- /dev/null
+++ b/lib/gitlab/blacklist.rb
@@ -0,0 +1,9 @@
+module Gitlab
+ module Blacklist
+ extend self
+
+ def path
+ %w(admin dashboard groups help profile projects search public assets u s teams merge_requests issues users snippets services repository hooks notes)
+ end
+ end
+end
diff --git a/lib/gitlab/diff_parser.rb b/lib/gitlab/diff_parser.rb
new file mode 100644
index 00000000000..fb27280c4a4
--- /dev/null
+++ b/lib/gitlab/diff_parser.rb
@@ -0,0 +1,77 @@
+module Gitlab
+ class DiffParser
+ include Enumerable
+
+ attr_reader :lines, :new_path
+
+ def initialize(diff)
+ @lines = diff.diff.lines.to_a
+ @new_path = diff.new_path
+ end
+
+ def each
+ line_old = 1
+ line_new = 1
+ type = nil
+
+ lines_arr = ::Gitlab::InlineDiff.processing lines
+ lines_arr.each do |line|
+ raw_line = line.dup
+
+ next if line.match(/^\-\-\- \/dev\/null/)
+ next if line.match(/^\+\+\+ \/dev\/null/)
+ next if line.match(/^\-\-\- a/)
+ next if line.match(/^\+\+\+ b/)
+
+ full_line = html_escape(line.gsub(/\n/, ''))
+ full_line = ::Gitlab::InlineDiff.replace_markers full_line
+
+ if line.match(/^@@ -/)
+ type = "match"
+
+ line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
+ line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
+
+ next if line_old == 1 && line_new == 1 #top of file
+ yield(full_line, type, nil, nil, nil)
+ next
+ else
+ type = identification_type(line)
+ line_code = generate_line_code(new_path, line_new, line_old)
+ yield(full_line, type, line_code, line_new, line_old, raw_line)
+ end
+
+
+ if line[0] == "+"
+ line_new += 1
+ elsif line[0] == "-"
+ line_old += 1
+ else
+ line_new += 1
+ line_old += 1
+ end
+ end
+ end
+
+ private
+
+ def identification_type(line)
+ if line[0] == "+"
+ "new"
+ elsif line[0] == "-"
+ "old"
+ else
+ nil
+ end
+ end
+
+ def generate_line_code(path, line_new, line_old)
+ "#{Digest::SHA1.hexdigest(path)}_#{line_old}_#{line_new}"
+ end
+
+ def html_escape str
+ replacements = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
+ str.gsub(/[&"'><]/, replacements)
+ end
+ end
+end
diff --git a/lib/gitlab/git_stats.rb b/lib/gitlab/git_stats.rb
deleted file mode 100644
index 855bffb5dde..00000000000
--- a/lib/gitlab/git_stats.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-module Gitlab
- class GitStats
- attr_accessor :repo, :ref
-
- def initialize repo, ref
- @repo, @ref = repo, ref
- end
-
- def authors
- @authors ||= collect_authors
- end
-
- def commits_count
- @commits_count ||= repo.commit_count(ref)
- end
-
- def files_count
- args = [ref, '-r', '--name-only' ]
- repo.git.run(nil, 'ls-tree', nil, {}, args).split("\n").count
- end
-
- def authors_count
- authors.size
- end
-
- def graph
- @graph ||= build_graph
- end
-
- protected
-
- def collect_authors
- shortlog = repo.git.shortlog({e: true, s: true }, ref)
-
- authors = []
-
- lines = shortlog.split("\n")
-
- lines.each do |line|
- data = line.split("\t")
- commits = data.first
- author = Grit::Actor.from_string(data.last)
-
- authors << OpenStruct.new(
- name: author.name,
- email: author.email,
- commits: commits.to_i
- )
- end
-
- authors.sort_by(&:commits).reverse
- end
-
- def build_graph n = 4
- from, to = (Date.today - n.weeks), Date.today
- args = ['--all', "--since=#{from.to_s(:date)}", '--format=%ad' ]
- rev_list = repo.git.run(nil, 'rev-list', nil, {}, args).split("\n")
-
- commits_dates = rev_list.values_at(* rev_list.each_index.select {|i| i.odd?})
- commits_dates = commits_dates.map { |date_str| Time.parse(date_str).to_date.to_s(:date) }
-
- commits_per_day = from.upto(to).map do |day|
- commits_dates.count(day.to_date.to_s(:date))
- end
-
- OpenStruct.new(
- labels: from.upto(to).map { |day| day.stamp('Aug 23') },
- commits: commits_per_day,
- weeks: n
- )
- end
- end
-end
diff --git a/lib/gitlab/graph/commit.rb b/lib/gitlab/graph/commit.rb
deleted file mode 100644
index 13c8ebc9952..00000000000
--- a/lib/gitlab/graph/commit.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require "grit"
-
-module Gitlab
- module Graph
- class Commit
- include ActionView::Helpers::TagHelper
-
- attr_accessor :time, :space, :refs, :parent_spaces
-
- def initialize(commit)
- @_commit = commit
- @time = -1
- @space = 0
- @parent_spaces = []
- end
-
- def method_missing(m, *args, &block)
- @_commit.send(m, *args, &block)
- end
-
- def to_graph_hash
- h = {}
- h[:parents] = self.parents.collect do |p|
- [p.id,0,0]
- end
- h[:author] = {
- name: author.name,
- email: author.email
- }
- h[:time] = time
- h[:space] = space
- h[:parent_spaces] = parent_spaces
- h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil?
- h[:id] = sha
- h[:date] = date
- h[:message] = message
- h
- end
-
- def add_refs(ref_cache, repo)
- if ref_cache.empty?
- repo.refs.each do |ref|
- ref_cache[ref.commit.id] ||= []
- ref_cache[ref.commit.id] << ref
- end
- end
- @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id)
- @refs ||= []
- end
- end
- end
-end
diff --git a/lib/gitlab/graph/json_builder.rb b/lib/gitlab/graph/json_builder.rb
deleted file mode 100644
index cc971a245a7..00000000000
--- a/lib/gitlab/graph/json_builder.rb
+++ /dev/null
@@ -1,268 +0,0 @@
-require "grit"
-
-module Gitlab
- module Graph
- class JsonBuilder
- attr_accessor :days, :commits, :ref_cache, :repo
-
- def self.max_count
- @max_count ||= 650
- end
-
- def initialize project, ref, commit
- @project = project
- @ref = ref
- @commit = commit
- @repo = project.repo
- @ref_cache = {}
-
- @commits = collect_commits
- @days = index_commits
- end
-
- def to_json(*args)
- {
- days: @days.compact.map { |d| [d.day, d.strftime("%b")] },
- commits: @commits.map(&:to_graph_hash)
- }.to_json(*args)
- end
-
- protected
-
- # Get commits from repository
- #
- def collect_commits
-
- @commits = Grit::Commit.find_all(repo, nil, {topo_order: true, max_count: self.class.max_count, skip: to_commit}).dup
-
- # Decorate with app/models/commit.rb
- @commits.map! { |commit| ::Commit.new(commit) }
-
- # Decorate with lib/gitlab/graph/commit.rb
- @commits.map! { |commit| Gitlab::Graph::Commit.new(commit) }
-
- # add refs to each commit
- @commits.each { |commit| commit.add_refs(ref_cache, repo) }
-
- @commits
- end
-
- # Method is adding time and space on the
- # list of commits. As well as returns date list
- # corelated with time set on commits.
- #
- # @param [Array<Graph::Commit>] commits to index
- #
- # @return [Array<TimeDate>] list of commit dates corelated with time on commits
- def index_commits
- days, times = [], []
- map = {}
-
- commits.reverse.each_with_index do |c,i|
- c.time = i
- days[i] = c.committed_date
- map[c.id] = c
- times[i] = c
- end
-
- @_reserved = {}
- days.each_index do |i|
- @_reserved[i] = []
- end
-
- commits_sort_by_ref.each do |commit|
- if map.include? commit.id then
- place_chain(map[commit.id], map)
- end
- end
-
- # find parent spaces for not overlap lines
- times.each do |c|
- c.parent_spaces.concat(find_free_parent_spaces(c, map, times))
- end
-
- days
- end
-
- # Skip count that the target commit is displayed in center.
- def to_commit
- commits = Grit::Commit.find_all(repo, nil, {topo_order: true})
- commit_index = commits.index do |c|
- c.id == @commit.id
- end
-
- if commit_index && (self.class.max_count / 2 < commit_index) then
- # get max index that commit is displayed in the center.
- commit_index - self.class.max_count / 2
- else
- 0
- end
- end
-
- def commits_sort_by_ref
- commits.sort do |a,b|
- if include_ref?(a)
- -1
- elsif include_ref?(b)
- 1
- else
- b.committed_date <=> a.committed_date
- end
- end
- end
-
- def include_ref?(commit)
- heads = commit.refs.select do |ref|
- ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag)
- end
-
- heads.map! do |head|
- head.name
- end
-
- heads.include?(@ref)
- end
-
- def find_free_parent_spaces(commit, map, times)
- spaces = []
-
- commit.parents.each do |p|
- if map.include?(p.id) then
- parent = map[p.id]
-
- range = if commit.time < parent.time then
- commit.time..parent.time
- else
- parent.time..commit.time
- end
-
- space = if commit.space >= parent.space then
- find_free_parent_space(range, parent.space, 1, commit.space, times)
- else
- find_free_parent_space(range, parent.space, -1, parent.space, times)
- end
-
- mark_reserved(range, space)
- spaces << space
- end
- end
-
- spaces
- end
-
- def find_free_parent_space(range, space_base, space_step, space_default, times)
- if is_overlap?(range, times, space_default) then
- find_free_space(range, space_base, space_step)
- else
- space_default
- end
- end
-
- def is_overlap?(range, times, overlap_space)
- range.each do |i|
- if i != range.first &&
- i != range.last &&
- times[i].space == overlap_space then
-
- return true;
- end
- end
-
- false
- end
-
- # Add space mark on commit and its parents
- #
- # @param [Graph::Commit] the commit object.
- # @param [Hash<String,Graph::Commit>] map of commits
- def place_chain(commit, map, parent_time = nil)
- leaves = take_left_leaves(commit, map)
- if leaves.empty?
- return
- end
- # and mark it as reserved
- min_time = leaves.last.time
- max_space = 1
- parents = leaves.last.parents.collect
- parents.each do |p|
- if map.include? p.id
- parent = map[p.id]
- if parent.time < min_time
- min_time = parent.time
- end
- if max_space < parent.space then
- max_space = parent.space
- end
- end
- end
- if parent_time.nil?
- max_time = leaves.first.time
- else
- max_time = parent_time - 1
- end
-
- time_range = leaves.last.time..leaves.first.time
- space = find_free_space(time_range, max_space, 2)
- leaves.each{|l| l.space = space}
-
- mark_reserved(min_time..max_time, space)
-
- # Visit branching chains
- leaves.each do |l|
- parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?}
- for p in parents
- place_chain(map[p.id], map, l.time)
- end
- end
- end
-
- def mark_reserved(time_range, space)
- for day in time_range
- @_reserved[day].push(space)
- end
- end
-
- def find_free_space(time_range, space_base, space_step)
- reserved = []
- for day in time_range
- reserved += @_reserved[day]
- end
- reserved.uniq!
-
- space = space_base
- while reserved.include?(space) do
- space += space_step
- if space <= 0 then
- space_step *= -1
- space = space_base + space_step
- end
- end
-
- space
- end
-
- # Takes most left subtree branch of commits
- # which don't have space mark yet.
- #
- # @param [Graph::Commit] the commit object.
- # @param [Hash<String,Graph::Commit>] map of commits
- #
- # @return [Array<Graph::Commit>] list of branch commits
- def take_left_leaves(commit, map)
- leaves = []
- leaves.push(commit) if commit.space.zero?
-
- while true
- return leaves if commit.parents.count.zero?
- return leaves unless map.include? commit.parents.first.id
-
- commit = map[commit.parents.first.id]
-
- return leaves unless commit.space.zero?
-
- leaves.push(commit)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb
new file mode 100644
index 00000000000..a1ff248a77f
--- /dev/null
+++ b/lib/gitlab/identifier.rb
@@ -0,0 +1,23 @@
+# Detect user based on identifier like
+# key-13 or user-36 or last commit
+module Gitlab
+ module Identifier
+ def identify(identifier, project, newrev)
+ if identifier.blank?
+ # Local push from gitlab
+ email = project.repository.commit(newrev).author_email rescue nil
+ User.find_by_email(email) if email
+
+ elsif identifier =~ /\Auser-\d+\Z/
+ # git push over http
+ user_id = identifier.gsub("user-", "")
+ User.find_by_id(user_id)
+
+ elsif identifier =~ /\Akey-\d+\Z/
+ # git push over ssh
+ key_id = identifier.gsub("key-", "")
+ Key.find_by_id(key_id).try(:user)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb
index 7a0a3214aa1..89c8e0680c3 100644
--- a/lib/gitlab/inline_diff.rb
+++ b/lib/gitlab/inline_diff.rb
@@ -4,7 +4,7 @@ module Gitlab
START = "#!idiff-start!#"
FINISH = "#!idiff-finish!#"
-
+
def processing diff_arr
indexes = _indexes_of_changed_lines diff_arr
@@ -13,6 +13,9 @@ module Gitlab
second_line = diff_arr[index+2]
max_length = [first_line.size, second_line.size].max
+ # Skip inline diff if empty line was replaced with content
+ next if first_line == "-\n"
+
first_the_same_symbols = 0
(0..max_length + 1).each do |i|
first_the_same_symbols = i - 1
@@ -20,9 +23,19 @@ module Gitlab
break
end
end
+
first_token = first_line[0..first_the_same_symbols][1..-1]
- diff_arr[index+1].sub!(first_token, first_token + START)
- diff_arr[index+2].sub!(first_token, first_token + START)
+ start = first_token + START
+
+ if first_token.empty?
+ # In case if we remove string of spaces in commit
+ diff_arr[index+1].sub!("-", "-" => "-#{START}")
+ diff_arr[index+2].sub!("+", "+" => "+#{START}")
+ else
+ diff_arr[index+1].sub!(first_token, first_token => start)
+ diff_arr[index+2].sub!(first_token, first_token => start)
+ end
+
last_the_same_symbols = 0
(1..max_length + 1).each do |i|
last_the_same_symbols = -i
@@ -60,8 +73,6 @@ module Gitlab
line.gsub!(FINISH, "</span>")
line
end
-
end
-
end
end
diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb
new file mode 100644
index 00000000000..bc49d27b521
--- /dev/null
+++ b/lib/gitlab/issues_labels.rb
@@ -0,0 +1,28 @@
+module Gitlab
+ class IssuesLabels
+ class << self
+ def important_labels
+ %w(bug critical confirmed)
+ end
+
+ def warning_labels
+ %w(documentation support)
+ end
+
+ def neutral_labels
+ %w(discussion suggestion)
+ end
+
+ def positive_labels
+ %w(feature enhancement)
+ end
+
+ def generate(project)
+ labels = important_labels + warning_labels + neutral_labels + positive_labels
+
+ project.issues_default_label_list = labels
+ project.save
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
new file mode 100644
index 00000000000..260bacfeeb0
--- /dev/null
+++ b/lib/gitlab/ldap/user.rb
@@ -0,0 +1,94 @@
+require 'gitlab/oauth/user'
+
+# LDAP extension for User model
+#
+# * Find or create user from omniauth.auth data
+# * Links LDAP account with existing user
+# * Auth LDAP user with login and password
+#
+module Gitlab
+ module LDAP
+ class User < Gitlab::OAuth::User
+ class << self
+ def find_or_create(auth)
+ @auth = auth
+
+ if uid.blank? || email.blank?
+ raise_error("Account must provide an uid and email address")
+ end
+
+ user = find(auth)
+
+ unless user
+ # Look for user with same emails
+ #
+ # Possible cases:
+ # * When user already has account and need to link his LDAP account.
+ # * LDAP uid changed for user with same email and we need to update his uid
+ #
+ user = find_user(email)
+
+ if user
+ user.update_attributes(extern_uid: uid, provider: provider)
+ log.info("(LDAP) Updating legacy LDAP user #{email} with extern_uid => #{uid}")
+ else
+ # Create a new user inside GitLab database
+ # based on LDAP credentials
+ #
+ #
+ user = create(auth)
+ end
+ end
+
+ user
+ end
+
+ def find_user(email)
+ user = model.find_by_email(email)
+
+ # If no user found and allow_username_or_email_login is true
+ # we look for user by extracting part of his email
+ if !user && email && ldap_conf['allow_username_or_email_login']
+ uname = email.partition('@').first
+ user = model.find_by_username(uname)
+ end
+
+ user
+ end
+
+ def authenticate(login, password)
+ # Check user against LDAP backend if user is not authenticated
+ # Only check with valid login and password to prevent anonymous bind results
+ return nil unless ldap_conf.enabled && login.present? && password.present?
+
+ ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf)
+ ldap_user = ldap.bind_as(
+ filter: Net::LDAP::Filter.eq(ldap.uid, login),
+ size: 1,
+ password: password
+ )
+
+ find_by_uid(ldap_user.dn) if ldap_user
+ end
+
+ private
+
+ def find_by_uid(uid)
+ model.where(provider: provider, extern_uid: uid).last
+ end
+
+ def provider
+ 'ldap'
+ end
+
+ def raise_error(message)
+ raise OmniAuth::Error, "(LDAP) " + message
+ end
+
+ def ldap_conf
+ Gitlab.config.ldap
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index e7d6e3e6bd9..dc9c0e0ab2c 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -7,6 +7,7 @@ module Gitlab
# Supported reference formats are:
# * @foo for team members
# * #123 for issues
+ # * #JIRA-123 for Jira issues
# * !123 for merge requests
# * $123 for snippets
# * 123456 for commits
@@ -17,7 +18,7 @@ module Gitlab
# Examples
#
# >> gfm("Hey @david, can you fix this?")
- # => "Hey <a href="/gitlab/team_members/1">@david</a>, can you fix this?"
+ # => "Hey <a href="/u/david">@david</a>, can you fix this?"
#
# >> gfm("Commit 35d5f7c closes #1234")
# => "Commit <a href="/gitlab/commits/35d5f7c">35d5f7c</a> closes <a href="/gitlab/issues/1234">#1234</a>"
@@ -25,6 +26,8 @@ module Gitlab
# >> gfm(":trollface:")
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
module Markdown
+ include IssuesHelper
+
attr_reader :html_options
# Public: Parse the provided text with GitLab-Flavored Markdown
@@ -60,7 +63,7 @@ module Gitlab
insert_piece($1)
end
- sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class)
+ sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class), tags: ActionView::Base.sanitized_allowed_tags + %w(table tr td th)
end
private
@@ -95,10 +98,11 @@ module Gitlab
(?<prefix>\W)? # Prefix
( # Reference
@(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
- |\#(?<issue>\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
+ |(?<skip>gfm-extraction-[\h]{6,40}) # Skip gfm extractions. Otherwise will be parsed as commit
)
(?<suffix>\W)? # Suffix
}x.freeze
@@ -111,13 +115,18 @@ module Gitlab
prefix = $~[:prefix]
suffix = $~[:suffix]
type = TYPES.select{|t| !$~[t].nil?}.first
- identifier = $~[type]
- # Avoid HTML entities
- if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
- match
- elsif ref_link = reference_link(type, identifier)
- "#{prefix}#{ref_link}#{suffix}"
+ if type
+ identifier = $~[type]
+
+ # Avoid HTML entities
+ if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
+ match
+ elsif ref_link = reference_link(type, identifier)
+ "#{prefix}#{ref_link}#{suffix}"
+ else
+ match
+ end
else
match
end
@@ -157,19 +166,22 @@ module Gitlab
end
def reference_user(identifier)
- if member = @project.users_projects.joins(:user).where(users: { username: identifier }).first
- link_to("@#{identifier}", project_team_member_url(@project, member), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member
+ 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
end
end
def reference_issue(identifier)
- if issue = @project.issues.where(id: identifier).first
- link_to("##{identifier}", project_issue_url(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}"))
+ if @project.issue_exists? identifier
+ url = url_for_issue(identifier)
+ title = title_for_issue(identifier)
+
+ link_to("##{identifier}", url, html_options.merge(title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}"))
end
end
def reference_merge_request(identifier)
- if merge_request = @project.merge_requests.where(id: identifier).first
+ if merge_request = @project.merge_requests.where(iid: identifier).first
link_to("!#{identifier}", project_merge_request_url(@project, merge_request), html_options.merge(title: "Merge Request: #{merge_request.title}", class: "gfm gfm-merge_request #{html_options[:class]}"))
end
end
@@ -182,7 +194,7 @@ module Gitlab
def reference_commit(identifier)
if @project.valid_repo? && commit = @project.repository.commit(identifier)
- link_to(identifier, project_commit_url(@project, commit), html_options.merge(title: CommitDecorator.new(commit).link_title, class: "gfm gfm-commit #{html_options[:class]}"))
+ link_to(identifier, project_commit_url(@project, commit), html_options.merge(title: commit.link_title, class: "gfm gfm-commit #{html_options[:class]}"))
end
end
end
diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb
new file mode 100644
index 00000000000..1b32b99f4ba
--- /dev/null
+++ b/lib/gitlab/oauth/user.rb
@@ -0,0 +1,85 @@
+# OAuth extension for User model
+#
+# * Find GitLab user based on omniauth uid and provider
+# * Create new user from omniauth data
+#
+module Gitlab
+ module OAuth
+ class User
+ class << self
+ attr_reader :auth
+
+ def find(auth)
+ @auth = auth
+ find_by_uid_and_provider
+ end
+
+ def create(auth)
+ @auth = auth
+ password = Devise.friendly_token[0, 8].downcase
+ opts = {
+ extern_uid: uid,
+ provider: provider,
+ name: name,
+ username: username,
+ email: email,
+ password: password,
+ password_confirmation: password,
+ }
+
+ user = model.build_user(opts, as: :admin)
+ user.save!
+ log.info "(OAuth) Creating user #{email} from login with extern_uid => #{uid}"
+
+ if Gitlab.config.omniauth['block_auto_created_users'] && !ldap?
+ user.block
+ end
+
+ user
+ end
+
+ private
+
+ def find_by_uid_and_provider
+ model.where(provider: provider, extern_uid: uid).last
+ end
+
+ def uid
+ auth.info.uid || auth.uid
+ end
+
+ def email
+ auth.info.email.downcase unless auth.info.email.nil?
+ end
+
+ def name
+ auth.info.name.to_s.force_encoding("utf-8")
+ end
+
+ def username
+ email.match(/^[^@]*/)[0]
+ end
+
+ def provider
+ auth.provider
+ end
+
+ def log
+ Gitlab::AppLogger
+ end
+
+ def model
+ ::User
+ end
+
+ def raise_error(message)
+ raise OmniAuth::Error, "(OAuth) " + message
+ end
+
+ def ldap?
+ provider == 'ldap'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb
index f2cfd8073e3..2f30fde2078 100644
--- a/lib/gitlab/popen.rb
+++ b/lib/gitlab/popen.rb
@@ -2,7 +2,7 @@ module Gitlab
module Popen
def popen(cmd, path)
vars = { "PWD" => path }
- options = { :chdir => path }
+ options = { chdir: path }
@cmd_output = ""
@cmd_status = 0
diff --git a/lib/gitlab/project_mover.rb b/lib/gitlab/project_mover.rb
deleted file mode 100644
index e21f45c6564..00000000000
--- a/lib/gitlab/project_mover.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# ProjectMover class
-#
-# Used for moving project repositories from one subdir to another
-module Gitlab
- class ProjectMover
- class ProjectMoveError < StandardError; end
-
- attr_reader :project, :old_dir, :new_dir
-
- def initialize(project, old_dir, new_dir)
- @project = project
- @old_dir = old_dir
- @new_dir = new_dir
- end
-
- def execute
- # Create new dir if missing
- new_dir_path = File.join(Gitlab.config.gitlab_shell.repos_path, new_dir)
- FileUtils.mkdir( new_dir_path, mode: 0770 ) unless File.exists?(new_dir_path)
-
- old_path = File.join(Gitlab.config.gitlab_shell.repos_path, old_dir, "#{project.path}.git")
- new_path = File.join(new_dir_path, "#{project.path}.git")
-
- if File.exists? new_path
- raise ProjectMoveError.new("Destination #{new_path} already exists")
- end
-
- begin
- FileUtils.mv( old_path, new_path )
- log_info "Project #{project.name} was moved from #{old_path} to #{new_path}"
- true
- rescue Exception => e
- message = "Project #{project.name} cannot be moved from #{old_path} to #{new_path}"
- log_info "Error! #{message} (#{e.message})"
- raise ProjectMoveError.new(message)
- end
- end
-
- protected
-
- def log_info message
- Gitlab::AppLogger.info message
- end
- end
-end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
new file mode 100644
index 00000000000..94b01e808d9
--- /dev/null
+++ b/lib/gitlab/reference_extractor.rb
@@ -0,0 +1,59 @@
+module Gitlab
+ # Extract possible GFM references from an arbitrary String for further processing.
+ class ReferenceExtractor
+ attr_accessor :users, :issues, :merge_requests, :snippets, :commits
+
+ include Markdown
+
+ def initialize
+ @users, @issues, @merge_requests, @snippets, @commits = [], [], [], [], []
+ end
+
+ def analyze string
+ parse_references(string.dup)
+ end
+
+ # Given a valid project, resolve the extracted identifiers of the requested type to
+ # model objects.
+
+ def users_for project
+ users.map do |identifier|
+ project.users.where(username: identifier).first
+ end.reject(&:nil?)
+ end
+
+ def issues_for project
+ issues.map do |identifier|
+ project.issues.where(iid: identifier).first
+ end.reject(&:nil?)
+ end
+
+ def merge_requests_for project
+ merge_requests.map do |identifier|
+ project.merge_requests.where(iid: identifier).first
+ end.reject(&:nil?)
+ end
+
+ def snippets_for project
+ snippets.map do |identifier|
+ project.snippets.where(id: identifier).first
+ end.reject(&:nil?)
+ end
+
+ def commits_for project
+ repo = project.repository
+ return [] if repo.nil?
+
+ commits.map do |identifier|
+ repo.commit(identifier)
+ end.reject(&:nil?)
+ end
+
+ private
+
+ def reference_link type, identifier
+ # Append identifier to the appropriate collection.
+ send("#{type}s") << identifier
+ end
+ end
+end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 483042205ea..b4be46d3b42 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -7,7 +7,11 @@ module Gitlab
end
def project_name_regex
- /\A[a-zA-Z][a-zA-Z0-9_\-\. ]*\z/
+ /\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z/
+ end
+
+ def name_regex
+ /\A[a-zA-Z0-9_\-\. ]*\z/
end
def path_regex
@@ -17,7 +21,7 @@ module Gitlab
protected
def default_regex
- /\A[a-zA-Z][a-zA-Z0-9_\-\.]*\z/
+ /\A[a-zA-Z0-9][a-zA-Z0-9_\-\.]*\z/
end
end
end
diff --git a/lib/gitlab/satellite/action.rb b/lib/gitlab/satellite/action.rb
index 63303ca3de1..5ea6f956765 100644
--- a/lib/gitlab/satellite/action.rb
+++ b/lib/gitlab/satellite/action.rb
@@ -25,25 +25,31 @@ module Gitlab
end
end
rescue Errno::ENOMEM => ex
- Gitlab::GitLogger.error(ex.message)
- return false
+ return handle_exception(ex)
rescue Grit::Git::GitTimeout => ex
- Gitlab::GitLogger.error(ex.message)
- return false
+ return handle_exception(ex)
ensure
Gitlab::ShellEnv.reset_env
end
- # * Clears the satellite
- # * Updates the satellite from Gitolite
+ # * Recreates the satellite
# * Sets up Git variables for the user
#
# Note: use this within #in_locked_and_timed_satellite
def prepare_satellite!(repo)
project.satellite.clear_and_update!
- repo.git.config({}, "user.name", user.name)
- repo.git.config({}, "user.email", user.email)
+ repo.config['user.name'] = user.name
+ repo.config['user.email'] = user.email
+ end
+
+ def default_options(options = {})
+ {raise: true, timeout: true}.merge(options)
+ end
+
+ def handle_exception(exception)
+ Gitlab::GitLogger.error(exception.message)
+ false
end
end
end
diff --git a/lib/gitlab/satellite/edit_file_action.rb b/lib/gitlab/satellite/edit_file_action.rb
index e9053f904c0..d793d0ba8dc 100644
--- a/lib/gitlab/satellite/edit_file_action.rb
+++ b/lib/gitlab/satellite/edit_file_action.rb
@@ -13,7 +13,7 @@ module Gitlab
# Updates the files content and creates a new commit for it
#
# Returns false if the ref has been updated while editing the file
- # Returns false if commiting the change fails
+ # Returns false if committing the change fails
# Returns false if pushing from the satellite to Gitolite failed or was rejected
# Returns true otherwise
def commit!(content, commit_message, last_commit)
@@ -49,7 +49,7 @@ module Gitlab
protected
def can_edit?(last_commit)
- current_last_commit = @project.repository.last_commit_for(ref, file_path).sha
+ current_last_commit = Gitlab::Git::Commit.last_for_path(@project.repository, ref, file_path).sha
last_commit == current_last_commit
end
end
diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb
index 832db6621c4..156483be8dd 100644
--- a/lib/gitlab/satellite/merge_action.rb
+++ b/lib/gitlab/satellite/merge_action.rb
@@ -5,48 +5,120 @@ module Gitlab
attr_accessor :merge_request
def initialize(user, merge_request)
- super user, merge_request.project
+ super user, merge_request.target_project
@merge_request = merge_request
end
# Checks if a merge request can be executed without user interaction
def can_be_merged?
in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
merge_in_satellite!(merge_repo)
end
end
# Merges the source branch into the target branch in the satellite and
- # pushes it back to Gitolite.
- # It also removes the source branch if requested in the merge request.
+ # pushes it back to the repository.
+ # It also removes the source branch if requested in the merge request (and this is permitted by the merge request).
#
# Returns false if the merge produced conflicts
- # Returns false if pushing from the satellite to Gitolite failed or was rejected
+ # Returns false if pushing from the satellite to the repository failed or was rejected
# Returns true otherwise
def merge!
in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
if merge_in_satellite!(merge_repo)
# push merge back to Gitolite
# will raise CommandFailed when push fails
- merge_repo.git.push({raise: true, timeout: true}, :origin, merge_request.target_branch)
-
+ merge_repo.git.push(default_options, :origin, merge_request.target_branch)
# remove source branch
if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch)
# will raise CommandFailed when push fails
- merge_repo.git.push({raise: true, timeout: true}, :origin, ":#{merge_request.source_branch}")
+ merge_repo.git.push(default_options, :origin, ":#{merge_request.source_branch}")
end
-
# merge, push and branch removal successful
true
end
end
rescue Grit::Git::CommandFailed => ex
- Gitlab::GitLogger.error(ex.message)
- false
+ handle_exception(ex)
end
- private
+ # Get a raw diff of the source to the target
+ def diff_in_satellite
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+
+ if merge_request.for_fork?
+ diff = merge_repo.git.native(:diff, default_options, "origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
+ else
+ diff = merge_repo.git.native(:diff, default_options, "#{merge_request.target_branch}", "#{merge_request.source_branch}")
+ end
+
+ return diff
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ # Only show what is new in the source branch compared to the target branch, not the other way around.
+ # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2)
+ # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
+ def diffs_between_satellite
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+ if merge_request.for_fork?
+ common_commit = merge_repo.git.native(:merge_base, default_options, ["origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}"]).strip
+ #this method doesn't take default options
+ diffs = merge_repo.diff(common_commit, "source/#{merge_request.source_branch}")
+ else
+ raise "Attempt to determine diffs between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
+ end
+ diffs = diffs.map { |diff| Gitlab::Git::Diff.new(diff) }
+ return diffs
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ # Get commit as an email patch
+ def format_patch
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+
+ if (merge_request.for_fork?)
+ patch = merge_repo.git.format_patch(default_options({stdout: true}), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}")
+ else
+ patch = merge_repo.git.format_patch(default_options({stdout: true}), "#{merge_request.target_branch}..#{merge_request.source_branch}")
+ end
+
+ return patch
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+ # Retrieve an array of commits between the source and the target
+ def commits_between
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+ if (merge_request.for_fork?)
+ commits = merge_repo.commits_between("origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
+ else
+ raise "Attempt to determine commits between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
+ end
+ commits = commits.map { |commit| Gitlab::Git::Commit.new(commit, nil) }
+ return commits
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ private
# Merges the source_branch into the target_branch in the satellite.
#
# Note: it will clear out the satellite before doing anything
@@ -54,18 +126,35 @@ module Gitlab
# Returns false if the merge produced conflicts
# Returns true otherwise
def merge_in_satellite!(repo)
- prepare_satellite!(repo)
-
- # create target branch in satellite at the corresponding commit from Gitolite
- repo.git.checkout({raise: true, timeout: true, b: true}, merge_request.target_branch, "origin/#{merge_request.target_branch}")
+ update_satellite_source_and_target!(repo)
- # merge the source branch from Gitolite into the satellite
+ # merge the source branch into the satellite
# will raise CommandFailed when merge fails
- repo.git.pull({raise: true, timeout: true, no_ff: true}, :origin, merge_request.source_branch)
+ if merge_request.for_fork?
+ repo.git.pull(default_options({no_ff: true}), 'source', merge_request.source_branch)
+ else
+ repo.git.pull(default_options({no_ff: true}), 'origin', merge_request.source_branch)
+ end
rescue Grit::Git::CommandFailed => ex
- Gitlab::GitLogger.error(ex.message)
- false
+ handle_exception(ex)
end
+
+ # Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for merges, diffs etc
+ def update_satellite_source_and_target!(repo)
+ if merge_request.for_fork?
+ repo.remote_add('source', merge_request.source_project.repository.path_to_repo)
+ repo.remote_fetch('source')
+ repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}")
+ else
+ # We can't trust the input here being branch names, we can't always check it out because it could be a relative ref i.e. HEAD~3
+ # we could actually remove the if true, because it should never ever happen (as long as the satellite has been prepared)
+ repo.git.checkout(default_options, "#{merge_request.source_branch}")
+ repo.git.checkout(default_options, "#{merge_request.target_branch}")
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
end
end
end
diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb
index e7f7a7673b5..6cb7814fae5 100644
--- a/lib/gitlab/satellite/satellite.rb
+++ b/lib/gitlab/satellite/satellite.rb
@@ -1,5 +1,5 @@
module Gitlab
- class SatelliteNotExistError < StandardError; end
+ class SatelliteNotExistError < StandardError; end
module Satellite
class Satellite
@@ -24,8 +24,11 @@ module Gitlab
def clear_and_update!
raise_no_satellite unless exists?
- delete_heads!
+ File.exists? path
+ @repo = nil
clear_working_dir!
+ delete_heads!
+ remove_remotes!
update_from_source!
end
@@ -55,16 +58,18 @@ module Gitlab
raise_no_satellite unless exists?
File.open(lock_file, "w+") do |f|
- f.flock(File::LOCK_EX)
-
- Dir.chdir(path) do
- return yield
+ begin
+ f.flock File::LOCK_EX
+ Dir.chdir(path) { return yield }
+ ensure
+ f.flock File::LOCK_UN
end
end
end
def lock_file
- Rails.root.join("tmp", "satellite_#{project.id}.lock")
+ create_locks_dir unless File.exists?(lock_files_dir)
+ File.join(lock_files_dir, "satellite_#{project.id}.lock")
end
def path
@@ -99,20 +104,44 @@ module Gitlab
if heads.include? PARKING_BRANCH
repo.git.checkout({}, PARKING_BRANCH)
else
- repo.git.checkout({b: true}, PARKING_BRANCH)
+ repo.git.checkout(default_options({b: true}), PARKING_BRANCH)
end
# remove the parking branch from the list of heads ...
heads.delete(PARKING_BRANCH)
# ... and delete all others
- heads.each { |head| repo.git.branch({D: true}, head) }
+ heads.each { |head| repo.git.branch(default_options({D: true}), head) }
+ end
+
+ # Deletes all remotes except origin
+ #
+ # This ensures we have no remote name clashes or issues updating branches when
+ # working with the satellite.
+ def remove_remotes!
+ remotes = repo.git.remote.split(' ')
+ remotes.delete('origin')
+ remotes.each { |name| repo.git.remote(default_options,'rm', name)}
end
# Updates the satellite from Gitolite
#
# Note: this will only update remote branches (i.e. origin/*)
def update_from_source!
- repo.git.fetch({timeout: true}, :origin)
+ repo.git.fetch(default_options, :origin)
+ end
+
+ def default_options(options = {})
+ {raise: true, timeout: true}.merge(options)
+ end
+
+ # Create directory for storing
+ # satellites lock files
+ def create_locks_dir
+ FileUtils.mkdir_p(lock_files_dir)
+ end
+
+ def lock_files_dir
+ @lock_files_dir ||= File.join(Gitlab.config.satellites.path, "tmp")
end
end
end
diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb
index 7f833867e39..89604162304 100644
--- a/lib/gitlab/theme.rb
+++ b/lib/gitlab/theme.rb
@@ -1,12 +1,18 @@
module Gitlab
class Theme
+ BASIC = 1
+ MARS = 2
+ MODERN = 3
+ GRAY = 4
+ COLOR = 5
+
def self.css_class_by_id(id)
themes = {
- 1 => "ui_basic",
- 2 => "ui_mars",
- 3 => "ui_modern",
- 4 => "ui_gray",
- 5 => "ui_color"
+ BASIC => "ui_basic",
+ MARS => "ui_mars",
+ MODERN => "ui_modern",
+ GRAY => "ui_gray",
+ COLOR => "ui_color"
}
id ||= 1
diff --git a/lib/gitlab/user_team_manager.rb b/lib/gitlab/user_team_manager.rb
deleted file mode 100644
index a8ff4a3d94d..00000000000
--- a/lib/gitlab/user_team_manager.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-# UserTeamManager class
-#
-# Used for manage User teams with project repositories
-module Gitlab
- class UserTeamManager
- class << self
- def assign(team, project, access)
- project = Project.find(project) unless project.is_a? Project
- searched_project = team.user_team_project_relationships.find_by_project_id(project.id)
-
- unless searched_project.present?
- team.user_team_project_relationships.create(project_id: project.id, greatest_access: access)
- update_team_users_access_in_project(team, project)
- end
- end
-
- def resign(team, project)
- project = Project.find(project) unless project.is_a? Project
-
- team.user_team_project_relationships.with_project(project).destroy_all
-
- update_team_users_access_in_project(team, project)
- end
-
- def update_team_user_membership(team, member, options)
- updates = {}
-
- if options[:default_projects_access] && options[:default_projects_access] != team.default_projects_access(member)
- updates[:permission] = options[:default_projects_access]
- end
-
- if options[:group_admin].to_s != team.admin?(member).to_s
- updates[:group_admin] = options[:group_admin].present?
- end
-
- unless updates.blank?
- user_team_relationship = team.user_team_user_relationships.find_by_user_id(member)
- if user_team_relationship.update_attributes(updates)
- if updates[:permission]
- rebuild_project_permissions_to_member(team, member)
- end
- true
- else
- false
- end
- else
- true
- end
- end
-
- def update_project_greates_access(team, project, permission)
- project_relation = team.user_team_project_relationships.find_by_project_id(project)
- if permission != team.max_project_access(project)
- if project_relation.update_attributes(greatest_access: permission)
- update_team_users_access_in_project(team, project)
- true
- else
- false
- end
- else
- true
- end
- end
-
- def rebuild_project_permissions_to_member(team, member)
- team.projects.each do |project|
- update_team_user_access_in_project(team, member, project)
- end
- end
-
- def update_team_users_access_in_project(team, project)
- members = team.members
- members.each do |member|
- update_team_user_access_in_project(team, member, project)
- end
- end
-
- def update_team_user_access_in_project(team, user, project)
- granted_access = max_teams_member_permission_in_project(user, project)
-
- project_team_user = UsersProject.find_by_user_id_and_project_id(user.id, project.id)
- project_team_user.destroy if project_team_user.present?
-
- # project_team_user.project_access != granted_access
- project.team << [user, granted_access] if granted_access > 0
- end
-
- def max_teams_member_permission_in_project(user, project, teams = nil)
- result_access = 0
-
- user_teams = project.user_teams.with_member(user)
-
- teams ||= user_teams
-
- if teams.any?
- teams.each do |team|
- granted_access = max_team_member_permission_in_project(team, user, project)
- result_access = [granted_access, result_access].max
- end
- end
- result_access
- end
-
- def max_team_member_permission_in_project(team, user, project)
- member_access = team.default_projects_access(user)
- team_access = team.user_team_project_relationships.find_by_project_id(project.id).greatest_access
-
- [team_access, member_access].min
- end
-
- def add_member_into_team(team, user, access, admin)
- user = User.find(user) unless user.is_a? User
-
- team.user_team_user_relationships.create(user_id: user.id, permission: access, group_admin: admin)
- team.projects.each do |project|
- update_team_user_access_in_project(team, user, project)
- end
- end
-
- def remove_member_from_team(team, user)
- user = User.find(user) unless user.is_a? User
-
- team.user_team_user_relationships.with_user(user).destroy_all
- other_teams = []
- team.projects.each do |project|
- other_teams << project.user_teams.with_member(user)
- end
- other_teams.uniq
- unless other_teams.any?
- UsersProject.in_projects(team.projects).with_user(user).destroy_all
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/version_info.rb b/lib/gitlab/version_info.rb
new file mode 100644
index 00000000000..6ee41e85cc9
--- /dev/null
+++ b/lib/gitlab/version_info.rb
@@ -0,0 +1,54 @@
+module Gitlab
+ class VersionInfo
+ include Comparable
+
+ attr_reader :major, :minor, :patch
+
+ def self.parse(str)
+ if str && m = str.match(/(\d+)\.(\d+)\.(\d+)/)
+ VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i)
+ else
+ VersionInfo.new
+ end
+ end
+
+ def initialize(major = 0, minor = 0, patch = 0)
+ @major = major
+ @minor = minor
+ @patch = patch
+ end
+
+ def <=>(other)
+ return unless other.is_a? VersionInfo
+ return unless valid? && other.valid?
+
+ if other.major < @major
+ 1
+ elsif @major < other.major
+ -1
+ elsif other.minor < @minor
+ 1
+ elsif @minor < other.minor
+ -1
+ elsif other.patch < @patch
+ 1
+ elsif @patch < other.patch
+ -1
+ else
+ 0
+ end
+ end
+
+ def to_s
+ if valid?
+ "%d.%d.%d" % [@major, @minor, @patch]
+ else
+ "Unknown"
+ end
+ end
+
+ def valid?
+ @major >= 0 && @minor >= 0 && @patch >= 0 && @major + @minor + @patch > 0
+ end
+ end
+end
diff --git a/lib/gitolited.rb b/lib/gitolited.rb
deleted file mode 100644
index a7fc4148106..00000000000
--- a/lib/gitolited.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# == Gitolited mixin
-#
-# Provide a shortcut to Gitlab::Shell instance by gitlab_shell
-#
-# Used by Project, UsersProject, etc
-#
-module Gitolited
- def gitlab_shell
- Gitlab::Shell.new
- end
-end
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index 4f2c86e2d41..d9c2d3b626d 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -11,7 +11,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
def block_code(code, language)
options = { options: {encoding: 'utf-8'} }
- options.merge!(lexer: language.downcase) if Pygments::Lexer.find(language)
+ lexer = Pygments::Lexer.find(language) # language can be an alias
+ options.merge!(lexer: lexer.aliases[0].downcase) if lexer # downcase is required
# New lines are placed to fix an rendering issue
# with code wrapped inside <h1> tag for next case:
diff --git a/lib/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh
new file mode 100755
index 00000000000..0d2f8418bcf
--- /dev/null
+++ b/lib/support/deploy/deploy.sh
@@ -0,0 +1,44 @@
+# This is deploy script we use to update staging server
+# You can always modify it for your needs :)
+
+# If any command return non-zero status - stop deploy
+set -e
+
+echo 'Deploy: Stoping sidekiq..'
+cd /home/git/gitlab/ && sudo -u git -H bundle exec rake sidekiq:stop RAILS_ENV=production
+
+echo 'Deploy: Show deploy index page'
+sudo -u git -H cp /home/git/gitlab/public/deploy.html /home/git/gitlab/public/index.html
+
+echo 'Deploy: Starting backup...'
+cd /home/git/gitlab/ && sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+
+echo 'Deploy: Stop GitLab server'
+sudo service gitlab stop
+
+echo 'Deploy: Get latest code'
+cd /home/git/gitlab/
+
+# clean working directory
+sudo -u git -H git stash
+
+# change branch to
+sudo -u git -H git pull origin master
+
+echo 'Deploy: Bundle and migrate'
+
+# change it to your needs
+sudo -u git -H bundle --without postgres
+
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# return stashed changes (if necessary)
+# sudo -u git -H git stash pop
+
+
+echo 'Deploy: Starting GitLab server...'
+sudo service gitlab start
+
+sleep 10
+sudo -u git -H rm /home/git/gitlab/public/index.html
+echo 'Deploy: Done'
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
new file mode 100755
index 00000000000..0248284f8d5
--- /dev/null
+++ b/lib/support/init.d/gitlab
@@ -0,0 +1,262 @@
+#! /bin/sh
+
+# GITLAB
+# Maintainer: @randx
+# Authors: rovanion.luckey@gmail.com, @randx
+# App Version: 6.0
+
+### BEGIN INIT INFO
+# Provides: gitlab
+# Required-Start: $local_fs $remote_fs $network $syslog redis-server
+# Required-Stop: $local_fs $remote_fs $network $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: GitLab git repository management
+# Description: GitLab git repository management
+### END INIT INFO
+
+### Environment variables
+RAILS_ENV="production"
+
+# Script variable names should be lower-case not to conflict with internal
+# /bin/sh variables such as PATH, EDITOR or SHELL.
+app_root="/home/git/gitlab"
+app_user="git"
+unicorn_conf="$app_root/config/unicorn.rb"
+pid_path="$app_root/tmp/pids"
+socket_path="$app_root/tmp/sockets"
+web_server_pid_path="$pid_path/unicorn.pid"
+sidekiq_pid_path="$pid_path/sidekiq.pid"
+
+
+
+### Here ends user configuration ###
+
+
+# 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;
+fi
+
+# Switch to the gitlab path, if it fails exit with an error.
+if ! cd "$app_root" ; then
+ echo "Failed to cd into $app_root, exiting!"; exit 1
+fi
+
+### Init Script functions
+
+check_pids(){
+ if ! mkdir -p "$pid_path"; then
+ echo "Could not create the path $pid_path needed to store the pids."
+ exit 1
+ fi
+ # If there exists a file which should hold the value of the Unicorn pid: read it.
+ if [ -f "$web_server_pid_path" ]; then
+ wpid=$(cat "$web_server_pid_path")
+ else
+ wpid=0
+ fi
+ if [ -f "$sidekiq_pid_path" ]; then
+ spid=$(cat "$sidekiq_pid_path")
+ else
+ spid=0
+ fi
+}
+
+# We use the pids in so many parts of the script it makes sense to always check them.
+# Only after start() is run should the pids change. Sidekiq sets it's own pid.
+check_pids
+
+
+# Checks whether the different parts of the service are already running or not.
+check_status(){
+ check_pids
+ # If the web server is running kill -0 $wpid returns true, or rather 0.
+ # Checks of *_status should only check for == 0 or != 0, never anything else.
+ if [ $wpid -ne 0 ]; then
+ kill -0 "$wpid" 2>/dev/null
+ web_status="$?"
+ else
+ web_status="-1"
+ fi
+ if [ $spid -ne 0 ]; then
+ kill -0 "$spid" 2>/dev/null
+ sidekiq_status="$?"
+ else
+ sidekiq_status="-1"
+ fi
+}
+
+# Check for stale pids and remove them if necessary
+check_stale_pids(){
+ check_status
+ # If there is a pid it is something else than 0, the service is running if
+ # *_status is == 0.
+ if [ "$wpid" != "0" -a "$web_status" != "0" ]; then
+ echo "Removing stale Unicorn web server pid. This is most likely caused by the web server crashing the last time it ran."
+ if ! rm "$web_server_pid_path"; then
+ echo "Unable to remove stale pid, exiting"
+ exit 1
+ 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."
+ if ! rm "$sidekiq_pid_path"; then
+ echo "Unable to remove stale pid, exiting"
+ exit 1
+ fi
+ fi
+}
+
+# If no parts of the service is running, bail out.
+exit_if_not_running(){
+ check_stale_pids
+ if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then
+ echo "GitLab is not running."
+ exit
+ fi
+}
+
+# Starts Unicorn and Sidekiq.
+start() {
+ check_stale_pids
+
+ # Then check if the service is running. If it is: don't start again.
+ if [ "$web_status" = "0" ]; then
+ echo "The Unicorn web server already running with pid $wpid, not restarting."
+ else
+ echo "Starting the GitLab Unicorn web server..."
+ # Remove old socket if it exists
+ rm -f "$socket_path"/gitlab.socket 2>/dev/null
+ # Start the webserver
+ bundle exec unicorn_rails -D -c "$unicorn_conf" -E "$RAILS_ENV"
+ fi
+
+ # If sidekiq is already running, don't start it again.
+ if [ "$sidekiq_status" = "0" ]; then
+ echo "The Sidekiq job dispatcher is already running with pid $spid, not restarting"
+ else
+ echo "Starting the GitLab Sidekiq event dispatcher..."
+ RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:start
+ # We are sleeping a bit here because sidekiq is slow at writing it's pid
+ sleep 2
+ fi
+
+ # Finally check the status to tell wether or not GitLab is running
+ status
+}
+
+# Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them.
+stop() {
+ exit_if_not_running
+ # If the Unicorn web server is running, tell it to stop;
+ if [ "$web_status" = "0" ]; then
+ kill -QUIT "$wpid" &
+ echo "Stopping the GitLab Unicorn web server..."
+ stopping=true
+ else
+ echo "The Unicorn web was not running, doing nothing."
+ fi
+ # And do the same thing for the Sidekiq.
+ if [ "$sidekiq_status" = "0" ]; then
+ printf "Stopping Sidekiq job dispatcher."
+ RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:stop &
+ stopping=true
+ else
+ echo "The Sidekiq was not running, must have run out of breath."
+ fi
+
+
+ # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script.
+ while [ "$stopping" = "true" ]; do
+ sleep 1
+ check_status
+ if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then
+ printf "."
+ else
+ printf "\n"
+ break
+ fi
+ done
+ sleep 1
+ # Cleaning up unused pids
+ rm "$web_server_pid_path" 2>/dev/null
+ # rm "$sidekiq_pid_path" # Sidekiq seems to be cleaning up it's own pid.
+
+ status
+}
+
+# Returns the status of GitLab and it's components
+status() {
+ check_status
+ if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then
+ echo "GitLab is not running."
+ return
+ fi
+ if [ "$web_status" = "0" ]; then
+ echo "The GitLab Unicorn webserver with pid $wpid is running."
+ else
+ printf "The GitLab Unicorn webserver is \033[31mnot running\033[0m.\n"
+ fi
+ if [ "$sidekiq_status" = "0" ]; then
+ echo "The GitLab Sidekiq job dispatcher with pid $spid is running."
+ else
+ printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n"
+ fi
+ if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then
+ printf "GitLab and all its components are \033[32mup and running\033[0m.\n"
+ fi
+}
+
+reload(){
+ exit_if_not_running
+ if [ "$wpid" = "0" ];then
+ echo "The GitLab Unicorn Web server is not running thus its configuration can't be reloaded."
+ exit 1
+ fi
+ printf "Reloading GitLab Unicorn configuration... "
+ kill -USR2 "$wpid"
+ echo "Done."
+ echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..."
+ RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:stop
+ echo "Starting Sidekiq..."
+ RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:start
+ # Waiting 2 seconds for sidekiq to write it.
+ sleep 2
+ status
+}
+
+restart(){
+ check_status
+ if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then
+ stop
+ fi
+ start
+}
+
+
+## Finally the input handling.
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ restart
+ ;;
+ reload|force-reload)
+ reload
+ ;;
+ status)
+ status
+ ;;
+ *)
+ echo "Usage: service gitlab {start|stop|restart|reload|status}"
+ exit 1
+ ;;
+esac
+
+exit
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
new file mode 100644
index 00000000000..3e929c52990
--- /dev/null
+++ b/lib/support/nginx/gitlab
@@ -0,0 +1,39 @@
+# GITLAB
+# Maintainer: @randx
+# App Version: 5.0
+
+upstream gitlab {
+ server unix:/home/git/gitlab/tmp/sockets/gitlab.socket;
+}
+
+server {
+ listen *:80 default_server; # e.g., listen 192.168.1.1:80; In most cases *:80 is a good idea
+ server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com;
+ server_tokens off; # don't show the version number, a security best practice
+ root /home/git/gitlab/public;
+
+ # individual nginx logs for this gitlab vhost
+ access_log /var/log/nginx/gitlab_access.log;
+ error_log /var/log/nginx/gitlab_error.log;
+
+ location / {
+ # serve static files from defined root folder;.
+ # @gitlab is a named location for the upstream fallback, see below
+ try_files $uri $uri/index.html $uri.html @gitlab;
+ }
+
+ # if a file, which is not found in the root folder is requested,
+ # then the proxy pass the request to the upsteam (gitlab unicorn)
+ location @gitlab {
+ proxy_read_timeout 300; # https://github.com/gitlabhq/gitlabhq/issues/694
+ proxy_connect_timeout 300; # https://github.com/gitlabhq/gitlabhq/issues/694
+ proxy_redirect off;
+
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Host $http_host;
+ proxy_set_header X-Real-IP $remote_addr;
+
+ proxy_pass http://gitlab;
+ }
+}
+
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
new file mode 100644
index 00000000000..8320b9b2576
--- /dev/null
+++ b/lib/tasks/cache.rake
@@ -0,0 +1,6 @@
+namespace :cache do
+ desc "GITLAB | Clear redis cache"
+ task :clear => :environment do
+ Rails.cache.clear
+ end
+end
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
new file mode 100644
index 00000000000..7d3602211c1
--- /dev/null
+++ b/lib/tasks/dev.rake
@@ -0,0 +1,10 @@
+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:shell:setup"].invoke
+ end
+end
+
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index 214ce720e7a..2eff1260b61 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -4,210 +4,75 @@ namespace :gitlab do
namespace :backup do
# Create backup of GitLab system
desc "GITLAB | Create a backup of the GitLab system"
- task :create => :environment do
+ task create: :environment do
warn_user_is_not_gitlab
Rake::Task["gitlab:backup:db:create"].invoke
Rake::Task["gitlab:backup:repo:create"].invoke
+ Rake::Task["gitlab:backup:uploads:create"].invoke
- Dir.chdir(Gitlab.config.backup.path)
-
- # saving additional informations
- 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/,"")
-
- File.open("#{Gitlab.config.backup.path}/backup_information.yml", "w+") do |file|
- file << s.to_yaml.gsub(/^---\n/,'')
- end
-
- # create archive
- print "Creating backup archive: #{Time.now.to_i}_gitlab_backup.tar ... "
- if Kernel.system("tar -cf #{Time.now.to_i}_gitlab_backup.tar repositories/ db/ backup_information.yml")
- puts "done".green
- else
- puts "failed".red
- end
-
- # cleanup: remove tmp files
- print "Deleting tmp directories ... "
- if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
- puts "done".green
- else
- puts "failed".red
- end
-
- # delete backups
- print "Deleting old backups ... "
- if Gitlab.config.backup.keep_time > 0
- file_list = Dir.glob("*_gitlab_backup.tar").map { |f| f.split(/_/).first.to_i }
- file_list.sort.each do |timestamp|
- if Time.at(timestamp) < (Time.now - Gitlab.config.backup.keep_time)
- %x{rm #{timestamp}_gitlab_backup.tar}
- end
- end
- puts "done".green
- else
- puts "skipping".yellow
- end
+ backup = Backup::Manager.new
+ backup.pack
+ backup.cleanup
+ backup.remove_old
end
# Restore backup of GitLab system
desc "GITLAB | Restore a previously created backup"
- task :restore => :environment do
+ task restore: :environment do
warn_user_is_not_gitlab
- Dir.chdir(Gitlab.config.backup.path)
-
- # check for existing backups in the backup dir
- file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
- puts "no backups found" if file_list.count == 0
- if file_list.count > 1 && ENV["BACKUP"].nil?
- puts "Found more than one backup, please specify which one you want to restore:"
- puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup"
- exit 1
- end
-
- tar_file = ENV["BACKUP"].nil? ? File.join("#{file_list.first}_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar")
-
- unless File.exists?(tar_file)
- puts "The specified backup doesn't exist!"
- exit 1
- end
-
- print "Unpacking backup ... "
- unless Kernel.system("tar -xf #{tar_file}")
- puts "failed".red
- exit 1
- else
- puts "done".green
- end
-
- settings = YAML.load_file("backup_information.yml")
- ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
-
- # 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
+ backup = Backup::Manager.new
+ backup.unpack
Rake::Task["gitlab:backup:db:restore"].invoke
Rake::Task["gitlab:backup:repo:restore"].invoke
+ Rake::Task["gitlab:backup:uploads:restore"].invoke
+ Rake::Task["gitlab:shell:setup"].invoke
- # cleanup: remove tmp files
- print "Deleting tmp directories ... "
- if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
- puts "done".green
- else
- puts "failed".red
- end
+ backup.cleanup
end
- ################################################################################
- ################################# invoked tasks ################################
-
- ################################# REPOSITORIES #################################
-
namespace :repo do
- task :create => :environment do
- backup_path_repo = File.join(Gitlab.config.backup.path, "repositories")
- FileUtils.mkdir_p(backup_path_repo) until Dir.exists?(backup_path_repo)
+ task create: :environment do
puts "Dumping repositories ...".blue
-
- Project.find_each(:batch_size => 1000) do |project|
- print " * #{project.path_with_namespace} ... "
-
- if project.empty_repo?
- puts "[SKIPPED]".cyan
- next
- end
-
- # Create namespace dir if missing
- FileUtils.mkdir_p(File.join(backup_path_repo, project.namespace.path)) if project.namespace
-
- # Build a destination path for backup
- path_to_bundle = File.join(backup_path_repo, project.path_with_namespace + ".bundle")
-
- if Kernel.system("cd #{project.repository.path_to_repo} > /dev/null 2>&1 && git bundle create #{path_to_bundle} --all > /dev/null 2>&1")
- puts "[DONE]".green
- else
- puts "[FAILED]".red
- end
- end
+ Backup::Repository.new.dump
+ puts "done".green
end
- task :restore => :environment do
- backup_path_repo = File.join(Gitlab.config.backup.path, "repositories")
- repos_path = Gitlab.config.gitlab_shell.repos_path
-
- puts "Restoring repositories ... "
-
- Project.find_each(:batch_size => 1000) do |project|
- print "#{project.path_with_namespace} ... "
-
- if project.namespace
- project.namespace.ensure_dir_exist
- end
-
- # Build a backup path
- path_to_bundle = File.join(backup_path_repo, project.path_with_namespace + ".bundle")
-
- if Kernel.system("git clone --bare #{path_to_bundle} #{project.repository.path_to_repo} > /dev/null 2>&1")
- puts "[DONE]".green
- else
- puts "[FAILED]".red
- end
- end
+ task restore: :environment do
+ puts "Restoring repositories ...".blue
+ Backup::Repository.new.restore
+ puts "done".green
end
end
- ###################################### DB ######################################
-
namespace :db do
- task :create => :environment do
- backup_path_db = File.join(Gitlab.config.backup.path, "db")
- FileUtils.mkdir_p(backup_path_db) unless Dir.exists?(backup_path_db)
-
- puts "Dumping database tables ... ".blue
- ActiveRecord::Base.connection.tables.each do |tbl|
- print " * #{tbl.yellow} ... "
- count = 1
- File.open(File.join(backup_path_db, tbl + ".yml"), "w+") do |file|
- ActiveRecord::Base.connection.select_all("SELECT * FROM `#{tbl}`").each do |line|
- line.delete_if{|k,v| v.blank?}
- output = {tbl + '_' + count.to_s => line}
- file << output.to_yaml.gsub(/^---\n/,'') + "\n"
- count += 1
- end
- puts "done".green
- end
- end
+ task create: :environment do
+ puts "Dumping database ... ".blue
+ Backup::Database.new.dump
+ puts "done".green
end
- task :restore => :environment do
- backup_path_db = File.join(Gitlab.config.backup.path, "db")
+ task restore: :environment do
+ puts "Restoring database ... ".blue
+ Backup::Database.new.restore
+ puts "done".green
+ end
+ end
- puts "Restoring database tables (loading fixtures) ... "
- Rake::Task["db:reset"].invoke
+ namespace :uploads do
+ task create: :environment do
+ puts "Dumping uploads ... ".blue
+ Backup::Uploads.new.dump
+ puts "done".green
+ end
- Dir.glob(File.join(backup_path_db, "*.yml") ).each do |dir|
- fixture_file = File.basename(dir, ".*" )
- print "#{fixture_file.yellow} ... "
- if File.size(dir) > 0
- ActiveRecord::Fixtures.create_fixtures(backup_path_db, fixture_file)
- puts "done".green
- else
- puts "skipping".yellow
- end
- end
+ task restore: :environment do
+ puts "Restoring uploads ... ".blue
+ Backup::Uploads.new.restore
+ puts "done".green
end
end
-
end # namespace end: backup
end # namespace end: gitlab
diff --git a/lib/tasks/gitlab/bulk_add_permission.rake b/lib/tasks/gitlab/bulk_add_permission.rake
index eb1a7559dbd..c270232edba 100644
--- a/lib/tasks/gitlab/bulk_add_permission.rake
+++ b/lib/tasks/gitlab/bulk_add_permission.rake
@@ -1,9 +1,9 @@
namespace :gitlab do
namespace :import do
desc "GITLAB | Add all users to all projects (admin users are added as masters)"
- task :all_users_to_all_projects => :environment do |t, args|
- user_ids = User.where(:admin => false).pluck(:id)
- admin_ids = User.where(:admin => true).pluck(:id)
+ task all_users_to_all_projects: :environment do |t, args|
+ user_ids = User.where(admin: false).pluck(:id)
+ admin_ids = User.where(admin: true).pluck(:id)
projects_ids = Project.pluck(:id)
puts "Importing #{user_ids.size} users into #{projects_ids.size} projects"
@@ -21,4 +21,4 @@ namespace :gitlab do
UsersProject.add_users_into_projects(project_ids, Array.wrap(user.id), UsersProject::DEVELOPER)
end
end
-end \ No newline at end of file
+end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 6a138396087..6e2a59f62ac 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -22,7 +22,10 @@ namespace :gitlab do
check_tmp_writable
check_init_script_exists
check_init_script_up_to_date
+ check_projects_have_namespace
check_satellites_exist
+ check_redis_version
+ check_git_version
finished_checking "GitLab"
end
@@ -136,13 +139,15 @@ namespace :gitlab do
def check_init_script_up_to_date
print "Init script up-to-date? ... "
+ recipe_path = Rails.root.join("lib/support/init.d/", "gitlab")
script_path = "/etc/init.d/gitlab"
+
unless File.exists?(script_path)
puts "can't check because of previous errors".magenta
return
end
- recipe_content = `curl https://raw.github.com/gitlabhq/gitlab-recipes/master/init.d/gitlab 2>/dev/null`
+ recipe_content = File.read(recipe_path)
script_content = File.read(script_path)
if recipe_content == script_content
@@ -217,7 +222,7 @@ namespace :gitlab do
puts "no".red
try_fixing_it(
"sudo chown -R gitlab #{log_path}",
- "sudo chmod -R rwX #{log_path}"
+ "sudo chmod -R u+rwX #{log_path}"
)
for_more_information(
see_installation_guide_section "GitLab"
@@ -237,7 +242,7 @@ namespace :gitlab do
puts "no".red
try_fixing_it(
"sudo chown -R gitlab #{tmp_path}",
- "sudo chmod -R rwX #{tmp_path}"
+ "sudo chmod -R u+rwX #{tmp_path}"
)
for_more_information(
see_installation_guide_section "GitLab"
@@ -245,6 +250,23 @@ namespace :gitlab do
fix_and_rerun
end
end
+
+ def check_redis_version
+ print "Redis version >= 2.0.0? ... "
+
+ if run_and_match("redis-cli --version", /redis-cli 2.\d.\d/)
+ puts "yes".green
+ else
+ puts "no".red
+ try_fixing_it(
+ "Update your redis server to a version >= 2.0.0"
+ )
+ for_more_information(
+ "gitlab-public-wiki/wiki/Trouble-Shooting-Guide in section sidekiq"
+ )
+ fix_and_rerun
+ end
+ end
end
@@ -255,7 +277,6 @@ namespace :gitlab do
warn_user_is_not_gitlab
start_checking "Environment"
- check_issue_1059_shell_profile_error
check_gitlab_git_config
check_python2_exists
check_python2_version
@@ -294,30 +315,6 @@ namespace :gitlab do
end
end
- # see https://github.com/gitlabhq/gitlabhq/issues/1059
- def check_issue_1059_shell_profile_error
- gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
- print "Has no \"-e\" in ~#{gitlab_shell_ssh_user}/.profile ... "
-
- profile_file = File.join(gitlab_shell_user_home, ".profile")
-
- unless File.read(profile_file) =~ /^-e PATH/
- puts "yes".green
- else
- puts "no".red
- try_fixing_it(
- "Open #{profile_file}",
- "Find the line starting with \"-e PATH\"",
- "Remove \"-e \" so the line starts with PATH"
- )
- for_more_information(
- see_installation_guide_section("Gitlab Shell"),
- "https://github.com/gitlabhq/gitlabhq/issues/1059"
- )
- fix_and_rerun
- end
- end
-
def check_python2_exists
print "Has python2? ... "
@@ -368,19 +365,21 @@ namespace :gitlab do
namespace :gitlab_shell do
- desc "GITLAB | Check the configuration of Gitlab Shell"
+ desc "GITLAB | Check the configuration of GitLab Shell"
task check: :environment do
warn_user_is_not_gitlab
- start_checking "Gitlab Shell"
+ start_checking "GitLab Shell"
+ check_gitlab_shell
check_repo_base_exists
check_repo_base_is_not_symlink
check_repo_base_user_and_group
check_repo_base_permissions
- check_post_receive_hook_is_up_to_date
- check_repos_post_receive_hooks_is_link
+ check_update_hook_is_up_to_date
+ check_repos_update_hooks_is_link
+ check_gitlab_shell_self_test
- finished_checking "Gitlab Shell"
+ finished_checking "GitLab Shell"
end
@@ -388,10 +387,10 @@ namespace :gitlab do
########################
- def check_post_receive_hook_is_up_to_date
- print "post-receive hook up-to-date? ... "
+ def check_update_hook_is_up_to_date
+ print "update hook up-to-date? ... "
- hook_file = "post-receive"
+ hook_file = "update"
gitlab_shell_hooks_path = Gitlab.config.gitlab_shell.hooks_path
gitlab_shell_hook_file = File.join(gitlab_shell_hooks_path, hook_file)
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
@@ -415,12 +414,12 @@ namespace :gitlab do
puts "no".red
puts "#{repo_base_path} is missing".red
try_fixing_it(
- "This should have been created when setting up Gitlab Shell.",
+ "This should have been created when setting up GitLab Shell.",
"Make sure it's set correctly in config/gitlab.yml",
- "Make sure Gitlab Shell is installed correctly."
+ "Make sure GitLab Shell is installed correctly."
)
for_more_information(
- see_installation_guide_section "Gitlab Shell"
+ see_installation_guide_section "GitLab Shell"
)
fix_and_rerun
end
@@ -465,7 +464,7 @@ namespace :gitlab do
"find #{repo_base_path} -type d -print0 | sudo xargs -0 chmod g+s"
)
for_more_information(
- see_installation_guide_section "Gitlab Shell"
+ see_installation_guide_section "GitLab Shell"
)
fix_and_rerun
end
@@ -482,25 +481,27 @@ namespace :gitlab do
return
end
- if File.stat(repo_base_path).uid == uid_for(gitlab_shell_ssh_user) &&
- File.stat(repo_base_path).gid == gid_for(gitlab_shell_owner_group)
+ uid = uid_for(gitlab_shell_ssh_user)
+ gid = gid_for(gitlab_shell_owner_group)
+ if File.stat(repo_base_path).uid == uid && File.stat(repo_base_path).gid == gid
puts "yes".green
else
puts "no".red
+ puts " User id for #{gitlab_shell_ssh_user}: #{uid}. Groupd id for #{gitlab_shell_owner_group}: #{gid}".blue
try_fixing_it(
"sudo chown -R #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group} #{repo_base_path}"
)
for_more_information(
- see_installation_guide_section "Gitlab Shell"
+ see_installation_guide_section "GitLab Shell"
)
fix_and_rerun
end
end
- def check_repos_post_receive_hooks_is_link
- print "post-receive hooks in repos are links: ... "
+ def check_repos_update_hooks_is_link
+ print "update hooks in repos are links: ... "
- hook_file = "post-receive"
+ hook_file = "update"
gitlab_shell_hooks_path = Gitlab.config.gitlab_shell.hooks_path
gitlab_shell_hook_file = File.join(gitlab_shell_hooks_path, hook_file)
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
@@ -540,7 +541,7 @@ namespace :gitlab do
File.realpath(project_hook_file) == File.realpath(gitlab_shell_hook_file)
puts "ok".green
else
- puts "not a link to Gitlab Shell's hook".red
+ puts "not a link to GitLab Shell's hook".red
try_fixing_it(
"sudo -u #{gitlab_shell_ssh_user} ln -sf #{gitlab_shell_hook_file} #{project_hook_file}"
)
@@ -553,6 +554,49 @@ namespace :gitlab do
end
end
+ def check_gitlab_shell_self_test
+ gitlab_shell_repo_base = File.expand_path('gitlab-shell', gitlab_shell_user_home)
+ check_cmd = File.expand_path('bin/check', gitlab_shell_repo_base)
+ puts "Running #{check_cmd}"
+ if system(check_cmd, chdir: gitlab_shell_repo_base)
+ puts 'gitlab-shell self-check successful'.green
+ else
+ puts 'gitlab-shell self-check failed'.red
+ try_fixing_it(
+ 'Make sure GitLab is running;',
+ 'Check the gitlab-shell configuration file:',
+ sudo_gitlab("editor #{File.expand_path('config.yml', gitlab_shell_repo_base)}")
+ )
+ fix_and_rerun
+ end
+ end
+
+ def check_projects_have_namespace
+ print "projects have namespace: ... "
+
+ unless Project.count > 0
+ puts "can't check, you have no projects".magenta
+ return
+ end
+ puts ""
+
+ Project.find_each(batch_size: 100) do |project|
+ print "#{project.name_with_namespace.yellow} ... "
+
+ if project.namespace
+ puts "yes".green
+ else
+ puts "no".red
+ try_fixing_it(
+ "Migrate global projects"
+ )
+ for_more_information(
+ "doc/update/5.4-to-6.0.md in section \"#global-projects\""
+ )
+ fix_and_rerun
+ end
+ end
+ end
# Helper methods
########################
@@ -582,6 +626,7 @@ namespace :gitlab do
start_checking "Sidekiq"
check_sidekiq_running
+ only_one_sidekiq_running
finished_checking "Sidekiq"
end
@@ -593,7 +638,7 @@ namespace :gitlab do
def check_sidekiq_running
print "Running? ... "
- if run_and_match("ps aux | grep -i sidekiq", /sidekiq \d\.\d\.\d.+$/)
+ if sidekiq_process_match
puts "yes".green
else
puts "no".red
@@ -607,6 +652,29 @@ namespace :gitlab do
fix_and_rerun
end
end
+
+ def only_one_sidekiq_running
+ sidekiq_match = sidekiq_process_match
+ return unless sidekiq_match
+
+ print 'Number of Sidekiq processes ... '
+ if sidekiq_match.length == 1
+ puts '1'.green
+ else
+ puts "#{sidekiq_match.length}".red
+ try_fixing_it(
+ 'sudo service gitlab stop',
+ 'sudo pkill -f sidekiq',
+ 'sleep 10 && sudo pkill -9 -f sidekiq',
+ 'sudo service gitlab start'
+ )
+ fix_and_rerun
+ end
+ end
+
+ def sidekiq_process_match
+ run_and_match("ps ux | grep -i sidekiq", /(sidekiq \d+\.\d+\.\d+.+$)/)
+ end
end
@@ -658,4 +726,34 @@ namespace :gitlab do
puts " #{step}"
end
end
+
+ def check_gitlab_shell
+ required_version = Gitlab::VersionInfo.new(1, 7, 1)
+ current_version = Gitlab::VersionInfo.parse(gitlab_shell_version)
+
+ print "GitLab Shell version >= #{required_version} ? ... "
+ if current_version.valid? && required_version <= current_version
+ puts "OK (#{current_version})".green
+ else
+ puts "FAIL. Please update gitlab-shell to #{required_version} from #{current_version}".red
+ end
+ end
+
+ def check_git_version
+ required_version = Gitlab::VersionInfo.new(1, 7, 10)
+ current_version = Gitlab::VersionInfo.parse(run("#{Gitlab.config.git.bin_path} --version"))
+
+ puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\""
+ print "Git version >= #{required_version} ? ... "
+
+ if current_version.valid? && required_version <= current_version
+ puts "yes (#{current_version})".green
+ else
+ puts "no".red
+ try_fixing_it(
+ "Update your git to a version >= #{required_version} from #{current_version}"
+ )
+ fix_and_rerun
+ end
+ end
end
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index d8ee56e5523..4aaab11340f 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -1,7 +1,7 @@
namespace :gitlab do
namespace :cleanup do
desc "GITLAB | Cleanup | Clean namespaces"
- task :dirs => :environment do
+ task dirs: :environment do
warn_user_is_not_gitlab
remove_flag = ENV['REMOVE']
@@ -43,8 +43,8 @@ namespace :gitlab do
end
end
- desc "GITLAB | Cleanup | Clean respositories"
- task :repos => :environment do
+ desc "GITLAB | Cleanup | Clean repositories"
+ task repos: :environment do
warn_user_is_not_gitlab
remove_flag = ENV['REMOVE']
diff --git a/lib/tasks/gitlab/enable_namespaces.rake b/lib/tasks/gitlab/enable_namespaces.rake
index a33639a0013..927748c0fd5 100644
--- a/lib/tasks/gitlab/enable_namespaces.rake
+++ b/lib/tasks/gitlab/enable_namespaces.rake
@@ -42,7 +42,7 @@ namespace :gitlab do
username = user.email.match(/^[^@]*/)[0]
username.gsub!("+", ".")
- # return username if no mathes
+ # return username if no matches
return username unless User.find_by_username(username)
# look for same username
@@ -99,7 +99,7 @@ namespace :gitlab do
end
begin
- Gitlab::ProjectMover.new(project, '', group.path).execute
+ project.transfer(group.path)
puts "moved to #{new_path}".green
rescue
puts "failed moving to #{new_path}".red
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index bddbd7ef855..8fa89270854 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -2,53 +2,76 @@ namespace :gitlab do
namespace :import do
# How to use:
#
- # 1. copy your bare repos under git base_path
+ # 1. copy your bare repos under git repos_path
# 2. run bundle exec rake gitlab:import:repos RAILS_ENV=production
#
# Notes:
# * project owner will be a first admin
# * existing projects will be skipped
#
- desc "GITLAB | Import bare repositories from git_host -> base_path into GitLab project instance"
- task :repos => :environment do
+ desc "GITLAB | Import bare repositories from gitlab_shell -> repos_path into GitLab project instance"
+ task repos: :environment do
git_base_path = Gitlab.config.gitlab_shell.repos_path
- repos_to_import = Dir.glob(git_base_path + '/*')
+ repos_to_import = Dir.glob(git_base_path + '/**/*.git')
namespaces = Namespace.pluck(:path)
repos_to_import.each do |repo_path|
- repo_name = File.basename repo_path
+ # strip repo base path
+ repo_path[0..git_base_path.length] = ''
- # Skip if group or user
- next if namespaces.include?(repo_name)
+ path = repo_path.sub(/\.git$/, '')
+ name = File.basename path
+ group_name = File.dirname path
+ group_name = nil if group_name == '.'
- # skip if not git repo
- next unless repo_name =~ /.git$/
+ # Skip if group or user
+ next if namespaces.include?(name)
- next if repo_name == 'gitolite-admin.git'
+ puts "Processing #{repo_path}".yellow
- path = repo_name.sub(/\.git$/, '')
+ if path =~ /.wiki\Z/
+ puts " * Skipping wiki repo"
+ next
+ end
project = Project.find_with_namespace(path)
- puts "Processing #{repo_name}".yellow
-
if project
- puts " * #{project.name} (#{repo_name}) exists"
+ puts " * #{project.name} (#{repo_path}) exists"
else
user = User.admins.first
project_params = {
- :name => path,
+ name: name,
+ path: name
}
+ # find group namespace
+ if group_name
+ group = Group.find_by_path(group_name)
+ # create group namespace
+ if !group
+ group = Group.new(:name => group_name)
+ group.path = group_name
+ group.owner = user
+ if group.save
+ puts " * Created Group #{group.name} (#{group.id})".green
+ else
+ puts " * Failed trying to create group #{group.name}".red
+ end
+ end
+ # set project group
+ project_params[:namespace_id] = group.id
+ end
+
project = Projects::CreateContext.new(user, project_params).execute
if project.valid?
- puts " * Created #{project.name} (#{repo_name})".green
+ puts " * Created #{project.name} (#{repo_path})".green
else
- puts " * Failed trying to create #{project.name} (#{repo_name})".red
+ puts " * Failed trying to create #{project.name} (#{repo_path})".red
end
end
end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index c44016ef6e8..ea83efcd887 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -40,8 +40,8 @@ namespace :gitlab do
puts ""
puts "GitLab information".yellow
- puts "Version:\t#{Gitlab::Version}"
- puts "Revision:\t#{Gitlab::Revision}"
+ puts "Version:\t#{Gitlab::VERSION}"
+ puts "Revision:\t#{Gitlab::REVISION}"
puts "Directory:\t#{Rails.root}"
puts "DB Adapter:\t#{database_adapter}"
puts "URL:\t\t#{Gitlab.config.gitlab.url}"
@@ -54,7 +54,7 @@ namespace :gitlab do
# check Gitolite version
- gitlab_shell_version_file = "#{Gitlab.config.gitlab_shell.repos_path}/../gitlab-shell/VERSION"
+ gitlab_shell_version_file = "#{Gitlab.config.gitlab_shell.hooks_path}/../VERSION"
if File.readable?(gitlab_shell_version_file)
gitlab_shell_version = File.read(gitlab_shell_version_file)
end
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index bc0742564d0..2b730774e06 100644
--- a/lib/tasks/gitlab/setup.rake
+++ b/lib/tasks/gitlab/setup.rake
@@ -1,16 +1,18 @@
namespace :gitlab do
desc "GITLAB | Setup production application"
- task :setup => :environment do
- setup
+ task setup: :environment do
+ setup_db
end
- def setup
+ def setup_db
warn_user_is_not_gitlab
- puts "This will create the necessary database tables and seed the database."
- puts "You will lose any previous data stored in the database."
- ask_to_continue
- puts ""
+ unless ENV['force'] == 'yes'
+ puts "This will create the necessary database tables and seed the database."
+ puts "You will lose any previous data stored in the database."
+ ask_to_continue
+ puts ""
+ end
Rake::Task["db:setup"].invoke
Rake::Task["db:seed_fu"].invoke
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 0ab8df1d094..0d7a390bc92 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -25,12 +25,14 @@ namespace :gitlab do
def setup
warn_user_is_not_gitlab
- puts "This will rebuild an authorized_keys file."
- puts "You will lose any data stored in /home/git/.ssh/authorized_keys."
- ask_to_continue
- puts ""
+ unless ENV['force'] == 'yes'
+ puts "This will rebuild an authorized_keys file."
+ puts "You will lose any data stored in authorized_keys file."
+ ask_to_continue
+ puts ""
+ end
- system("echo '# Managed by gitlab-shell' > /home/git/.ssh/authorized_keys")
+ 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)
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index cb4e34cc0d7..ac2c4577c77 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -16,7 +16,7 @@ namespace :gitlab do
# Check which OS is running
#
# It will primarily use lsb_relase to determine the OS.
- # It has fallbacks to Debian, SuSE and OS X.
+ # It has fallbacks to Debian, SuSE, OS X and systems running systemd.
def os_name
os_name = run("lsb_release -irs")
os_name ||= if File.readable?('/etc/system-release')
@@ -32,13 +32,16 @@ namespace :gitlab do
os_name ||= if os_x_version = run("sw_vers -productVersion")
"Mac OS X #{os_x_version}"
end
+ os_name ||= if File.readable?('/etc/os-release')
+ File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1]
+ end
os_name.try(:squish!)
end
# Prompt the user to input something
#
# message - the message to display before input
- # choices - array of strings of acceptible answers or nil for any answer
+ # choices - array of strings of acceptable answers or nil for any answer
#
# Returns the user's answer
def prompt(message, choices = nil)
@@ -49,10 +52,10 @@ namespace :gitlab do
answer
end
- # Runs the given command and matches the output agains the given pattern
+ # Runs the given command and matches the output against the given pattern
#
# Returns nil if nothing matched
- # Retunrs the MatchData if the pattern matched
+ # Returns the MatchData if the pattern matched
#
# see also #run
# see also String#match
@@ -77,7 +80,11 @@ namespace :gitlab do
end
def gid_for(group_name)
- Etc.getgrnam(group_name).gid
+ begin
+ Etc.getgrnam(group_name).gid
+ rescue ArgumentError # no group
+ "group #{group_name} doesn't exist"
+ end
end
def warn_user_is_not_gitlab
diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake
index ad1bfb2e4b3..03b3fc5ea20 100644
--- a/lib/tasks/gitlab/test.rake
+++ b/lib/tasks/gitlab/test.rake
@@ -1,4 +1,4 @@
namespace :gitlab do
desc "GITLAB | Run both spinach and rspec"
- task :test => ['spinach', 'spec']
+ task test: ['spinach', 'spec']
end
diff --git a/lib/tasks/migrate/migrate_iids.rake b/lib/tasks/migrate/migrate_iids.rake
new file mode 100644
index 00000000000..33271e1a2bb
--- /dev/null
+++ b/lib/tasks/migrate/migrate_iids.rake
@@ -0,0 +1,48 @@
+desc "GITLAB | Build internal ids for issues and merge requests"
+task migrate_iids: :environment do
+ puts 'Issues'.yellow
+ Issue.where(iid: nil).find_each(batch_size: 100) do |issue|
+ begin
+ issue.set_iid
+ if issue.update_attribute(:iid, issue.iid)
+ print '.'
+ else
+ print 'F'
+ end
+ rescue
+ print 'F'
+ end
+ end
+
+ puts 'done'
+ puts 'Merge Requests'.yellow
+ MergeRequest.where(iid: nil).find_each(batch_size: 100) do |mr|
+ begin
+ mr.set_iid
+ if mr.update_attribute(:iid, mr.iid)
+ print '.'
+ else
+ print 'F'
+ end
+ rescue => ex
+ print 'F'
+ end
+ end
+
+ puts 'done'
+ puts 'Milestones'.yellow
+ Milestone.where(iid: nil).find_each(batch_size: 100) do |m|
+ begin
+ m.set_iid
+ if m.update_attribute(:iid, m.iid)
+ print '.'
+ else
+ print 'F'
+ end
+ rescue
+ print 'F'
+ end
+ end
+
+ puts 'done'
+end
diff --git a/lib/tasks/sidekiq.rake b/lib/tasks/sidekiq.rake
index cf99951e027..d0e9dfe46a1 100644
--- a/lib/tasks/sidekiq.rake
+++ b/lib/tasks/sidekiq.rake
@@ -1,19 +1,19 @@
namespace :sidekiq do
desc "GITLAB | Stop sidekiq"
task :stop do
- run "bundle exec sidekiqctl stop #{pidfile}"
+ system "bundle exec sidekiqctl stop #{pidfile}"
end
desc "GITLAB | Start sidekiq"
task :start do
- run "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &"
+ system "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &"
end
-
+
desc "GITLAB | Start sidekiq with launchd on Mac OS X"
task :launchd do
- run "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1"
+ system "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1"
end
-
+
def pidfile
Rails.root.join("tmp", "pids", "sidekiq.pid")
end
diff --git a/lib/tasks/travis.rake b/lib/tasks/travis.rake
index 6b434830803..bc1b8aadbc5 100644
--- a/lib/tasks/travis.rake
+++ b/lib/tasks/travis.rake
@@ -1,5 +1,5 @@
desc "Travis run tests"
-task :travis => [
+task travis: [
:spinach,
:spec
]
diff --git a/public/deploy.html b/public/deploy.html
index d8c287809ea..d9c4bb5c583 100644
--- a/public/deploy.html
+++ b/public/deploy.html
@@ -5,7 +5,7 @@
<link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
- <h1>Deploy in progress</h1>
+ <h1><center><img src="/gitlab_logo.png"/></center>Deploy in progress</h1>
<h3>Please try again in few minutes or contact your administrator.</h3>
</body>
</html>
diff --git a/public/gitlab_logo.png b/public/gitlab_logo.png
new file mode 100644
index 00000000000..e3cda5978ab
--- /dev/null
+++ b/public/gitlab_logo.png
Binary files differ
diff --git a/script/check b/script/check
index d2eb4a2f6d8..c907a98b5d9 100755
--- a/script/check
+++ b/script/check
@@ -1,2 +1,2 @@
#!/bin/sh
-sudo -u gitlab -H bundle exec rake gitlab:check RAILS_ENV=production
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
diff --git a/spec/contexts/filter_context_spec.rb b/spec/contexts/filter_context_spec.rb
new file mode 100644
index 00000000000..db27742b9b5
--- /dev/null
+++ b/spec/contexts/filter_context_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe FilterContext do
+
+ let(:user) { create :user }
+ let(:user2) { create :user }
+ let(:project1) { create(:project, creator_id: user.id) }
+ let(:project2) { create(:project, creator_id: user.id) }
+ let(:merge_request1) { create(:merge_request, author_id: user.id, source_project: project1, target_project: project2) }
+ let(:merge_request2) { create(:merge_request, author_id: user.id, source_project: project2, target_project: project1) }
+ let(:merge_request3) { create(:merge_request, author_id: user.id, source_project: project2, target_project: project2) }
+ let(:merge_request4) { create(:merge_request, author_id: user2.id, source_project: project2, target_project: project2, target_branch:"notes_refactoring") }
+ let(:issue1) { create(:issue, assignee_id: user.id, project: project1) }
+ let(:issue2) { create(:issue, assignee_id: user.id, project: project2) }
+ let(:issue3) { create(:issue, assignee_id: user2.id, project: project2) }
+
+ describe 'merge requests' do
+ before :each do
+ merge_request1
+ merge_request2
+ merge_request3
+ merge_request4
+ end
+
+ it 'should by default filter properly' do
+ merge_requests = user.cared_merge_requests
+ params ={}
+ merge_requests = FilterContext.new(merge_requests, params).execute
+ merge_requests.size.should == 3
+ end
+
+ it 'should apply blocks passed in on creation to the filters' do
+ merge_requests = user.cared_merge_requests
+ params = {:project_id => project1.id}
+ merge_requests = FilterContext.new(merge_requests, params).execute
+ merge_requests.size.should == 1
+ end
+ end
+
+ describe 'issues' do
+ before :each do
+ issue1
+ issue2
+ issue3
+ end
+ it 'should by default filter projects properly' do
+ issues = user.assigned_issues
+ params = {}
+ issues = FilterContext.new(issues, params).execute
+ issues.size.should == 2
+ end
+ it 'should apply blocks passed in on creation to the filters' do
+ issues = user.assigned_issues
+ params = {:project_id => project1.id}
+ issues = FilterContext.new(issues, params).execute
+ issues.size.should == 1
+ end
+ end
+end
diff --git a/spec/contexts/fork_context_spec.rb b/spec/contexts/fork_context_spec.rb
new file mode 100644
index 00000000000..ed51b0c3f8e
--- /dev/null
+++ b/spec/contexts/fork_context_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe Projects::ForkContext do
+ describe :fork_by_user do
+ before do
+ @from_namespace = create(:namespace)
+ @from_user = create(:user, namespace: @from_namespace )
+ @from_project = create(:project, creator_id: @from_user.id, namespace: @from_namespace)
+ @to_namespace = create(:namespace)
+ @to_user = create(:user, namespace: @to_namespace)
+ end
+
+ context 'fork project' do
+
+ it "successfully creates project in the user namespace" do
+ @to_project = fork_project(@from_project, @to_user)
+
+ @to_project.owner.should == @to_user
+ @to_project.namespace.should == @to_user.namespace
+ end
+ end
+
+ context 'fork project failure' do
+
+ it "fails due to transaction failure" do
+ # make the mock gitlab-shell fail
+ @to_project = fork_project(@from_project, @to_user, false)
+
+ @to_project.errors.should_not be_empty
+ @to_project.errors[:base].should include("Fork transaction failed.")
+ end
+
+ end
+
+ context 'project already exists' do
+
+ it "should fail due to validation, not transaction failure" do
+ @existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace)
+ @to_project = fork_project(@from_project, @to_user)
+
+ @existing_project.persisted?.should be_true
+ @to_project.errors[:base].should include("Invalid fork destination")
+ @to_project.errors[:base].should_not include("Fork transaction failed.")
+ end
+
+ end
+ end
+
+ def fork_project(from_project, user, fork_success = true)
+ context = Projects::ForkContext.new(from_project, user)
+ shell = mock("gitlab_shell")
+ shell.stub(fork_repository: fork_success)
+ context.stub(gitlab_shell: shell)
+ context.execute
+ end
+
+end
diff --git a/spec/contexts/issues/bulk_update_context_spec.rb b/spec/contexts/issues/bulk_update_context_spec.rb
new file mode 100644
index 00000000000..058e43ba090
--- /dev/null
+++ b/spec/contexts/issues/bulk_update_context_spec.rb
@@ -0,0 +1,113 @@
+require 'spec_helper'
+
+describe Issues::BulkUpdateContext do
+ before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
+ after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
+
+ let(:issue) {
+ create(:issue, project: @project)
+ }
+
+ before do
+ @user = create :user
+ opts = {
+ name: "GitLab",
+ namespace: @user.namespace
+ }
+ @project = Projects::CreateContext.new(@user, opts).execute
+ end
+
+ describe :close_issue do
+
+ before do
+ @issues = 5.times.collect do
+ create(:issue, project: @project)
+ end
+ @params = {
+ update: {
+ status: 'closed',
+ issues_ids: @issues.map(&:id)
+ }
+ }
+ end
+
+ it {
+ result = Issues::BulkUpdateContext.new(@project, @user, @params).execute
+ result[:success].should be_true
+ result[:count].should == @issues.count
+
+ @project.issues.opened.should be_empty
+ @project.issues.closed.should_not be_empty
+ }
+
+ end
+
+ describe :reopen_issues do
+
+ before do
+ @issues = 5.times.collect do
+ create(:closed_issue, project: @project)
+ end
+ @params = {
+ update: {
+ status: 'reopen',
+ issues_ids: @issues.map(&:id)
+ }
+ }
+ end
+
+ it {
+ result = Issues::BulkUpdateContext.new(@project, @user, @params).execute
+ result[:success].should be_true
+ result[:count].should == @issues.count
+
+ @project.issues.closed.should be_empty
+ @project.issues.opened.should_not be_empty
+ }
+
+ end
+
+ describe :update_assignee do
+
+ before do
+ @new_assignee = create :user
+ @params = {
+ update: {
+ issues_ids: [issue.id],
+ assignee_id: @new_assignee.id
+ }
+ }
+ end
+
+ it {
+ result = Issues::BulkUpdateContext.new(@project, @user, @params).execute
+ result[:success].should be_true
+ result[:count].should == 1
+
+ @project.issues.first.assignee.should == @new_assignee
+ }
+
+ end
+
+ describe :update_milestone do
+
+ before do
+ @milestone = create :milestone
+ @params = {
+ update: {
+ issues_ids: [issue.id],
+ milestone_id: @milestone.id
+ }
+ }
+ end
+
+ it {
+ result = Issues::BulkUpdateContext.new(@project, @user, @params).execute
+ result[:success].should be_true
+ result[:count].should == 1
+
+ @project.issues.first.milestone.should == @milestone
+ }
+ end
+
+end
diff --git a/spec/contexts/projects_create_context_spec.rb b/spec/contexts/projects_create_context_spec.rb
index dd10dd3ede8..8b2a49dbee5 100644
--- a/spec/contexts/projects_create_context_spec.rb
+++ b/spec/contexts/projects_create_context_spec.rb
@@ -1,11 +1,15 @@
require 'spec_helper'
describe Projects::CreateContext do
+ before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
+ after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
+
describe :create_by_user do
before do
@user = create :user
@opts = {
- name: "GitLab"
+ name: "GitLab",
+ namespace: @user.namespace
}
end
@@ -21,15 +25,48 @@ describe Projects::CreateContext do
context 'group namespace' do
before do
- @group = create :group, owner: @user
+ @group = create :group
+ @group.add_owner(@user)
+
@opts.merge!(namespace_id: @group.id)
@project = create_project(@user, @opts)
end
it { @project.should be_valid }
- it { @project.owner.should == @user }
+ it { @project.owner.should == @group }
it { @project.namespace.should == @group }
end
+
+ context 'respect configured public setting' do
+ before(:each) do
+ @settings = double("settings")
+ @settings.stub(:issues) { true }
+ @settings.stub(:merge_requests) { true }
+ @settings.stub(:wiki) { true }
+ @settings.stub(:wall) { true }
+ @settings.stub(:snippets) { true }
+ stub_const("Settings", Class.new)
+ Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings)
+ end
+
+ context 'should be public when setting is public' do
+ before do
+ @settings.stub(:public) { true }
+ @project = create_project(@user, @opts)
+ end
+
+ it { @project.public.should be_true }
+ end
+
+ context 'should be private when setting is not public' do
+ before do
+ @settings.stub(:public) { false }
+ @project = create_project(@user, @opts)
+ end
+
+ it { @project.public.should be_false }
+ end
+ end
end
def create_project(user, opts)
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
new file mode 100644
index 00000000000..d528d12c66c
--- /dev/null
+++ b/spec/controllers/application_controller_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe ApplicationController do
+ describe '#check_password_expiration' do
+ let(:user) { create(:user) }
+ let(:controller) { ApplicationController.new }
+
+ it 'should redirect if the user is over their password expiry' do
+ user.password_expires_at = Time.new(2002)
+ user.ldap_user?.should be_false
+ controller.stub!(:current_user).and_return(user)
+ controller.should_receive(:redirect_to)
+ controller.should_receive(:new_profile_password_path)
+ controller.send(:check_password_expiration)
+ end
+
+ it 'should not redirect if the user is under their password expiry' do
+ user.password_expires_at = Time.now + 20010101
+ user.ldap_user?.should be_false
+ controller.stub!(:current_user).and_return(user)
+ controller.should_not_receive(:redirect_to)
+ controller.send(:check_password_expiration)
+ end
+
+ it 'should not redirect if the user is over their password expiry but they are an ldap user' do
+ user.password_expires_at = Time.new(2002)
+ user.stub!(:ldap_user?).and_return(true)
+ controller.stub!(:current_user).and_return(user)
+ controller.should_not_receive(:redirect_to)
+ controller.send(:check_password_expiration)
+ end
+ end
+end \ No newline at end of file
diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb
new file mode 100644
index 00000000000..479d8fc1a1d
--- /dev/null
+++ b/spec/controllers/blob_controller_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Projects::BlobController do
+ let(:project) { create(:project_with_code) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ project.team << [user, :master]
+
+ project.stub(:branches).and_return(['master', 'foo/bar/baz'])
+ project.stub(:tags).and_return(['v1.0.0', 'v2.0.0'])
+ controller.instance_variable_set(:@project, project)
+ end
+
+ describe "GET show" do
+ render_views
+
+ before { get :show, project_id: project.to_param, id: id }
+
+ context "valid branch, valid file" do
+ let(:id) { 'master/README.md' }
+ it { should respond_with(:success) }
+ end
+
+ context "valid branch, invalid file" do
+ let(:id) { 'master/invalid-path.rb' }
+ it { should respond_with(:not_found) }
+ end
+
+ context "invalid branch, valid file" do
+ let(:id) { 'invalid-branch/README.md' }
+ it { should respond_with(:not_found) }
+ end
+ end
+end
diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb
index 7bf13822829..fdf0884f4e2 100644
--- a/spec/controllers/commit_controller_spec.rb
+++ b/spec/controllers/commit_controller_spec.rb
@@ -1,20 +1,19 @@
require 'spec_helper'
-describe CommitController do
- let(:project) { create(:project) }
+describe Projects::CommitController do
+ let(:project) { create(:project_with_code) }
let(:user) { create(:user) }
- let(:commit) { project.repository.last_commit_for("master") }
+ let(:commit) { project.repository.commit("master") }
before do
sign_in(user)
-
project.team << [user, :master]
end
describe "#show" do
shared_examples "export as" do |format|
it "should generally work" do
- get :show, project_id: project.code, id: commit.id, format: format
+ get :show, project_id: project.to_param, id: commit.id, format: format
expect(response).to be_success
end
@@ -22,11 +21,11 @@ describe CommitController do
it "should generate it" do
Commit.any_instance.should_receive(:"to_#{format}")
- get :show, project_id: project.code, id: commit.id, format: format
+ get :show, project_id: project.to_param, id: commit.id, format: format
end
it "should render it" do
- get :show, project_id: project.code, id: commit.id, format: format
+ get :show, project_id: project.to_param, id: commit.id, format: format
expect(response.body).to eq(commit.send(:"to_#{format}"))
end
@@ -34,7 +33,7 @@ describe CommitController do
it "should not escape Html" do
Commit.any_instance.stub(:"to_#{format}").and_return('HTML entities &<>" ')
- get :show, project_id: project.code, id: commit.id, format: format
+ get :show, project_id: project.to_param, id: commit.id, format: format
expect(response.body).to_not include('&amp;')
expect(response.body).to_not include('&gt;')
@@ -48,7 +47,7 @@ describe CommitController do
let(:format) { :diff }
it "should really only be a git diff" do
- get :show, project_id: project.code, id: commit.id, format: format
+ get :show, project_id: project.to_param, id: commit.id, format: format
expect(response.body).to start_with("diff --git")
end
@@ -59,13 +58,13 @@ describe CommitController do
let(:format) { :patch }
it "should really be a git email patch" do
- get :show, project_id: project.code, id: commit.id, format: format
+ get :show, project_id: project.to_param, id: commit.id, format: format
expect(response.body).to start_with("From #{commit.id}")
end
it "should contain a git diff" do
- get :show, project_id: project.code, id: commit.id, format: format
+ get :show, project_id: project.to_param, id: commit.id, format: format
expect(response.body).to match(/^diff --git/)
end
diff --git a/spec/controllers/commits_controller_spec.rb b/spec/controllers/commits_controller_spec.rb
index 1d5d99df802..8263afc97a2 100644
--- a/spec/controllers/commits_controller_spec.rb
+++ b/spec/controllers/commits_controller_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-describe CommitsController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
+describe Projects::CommitsController do
+ let(:project) { create(:project_with_code) }
+ let(:user) { create(:user) }
before do
sign_in(user)
@@ -13,7 +13,7 @@ describe CommitsController do
describe "GET show" do
context "as atom feed" do
it "should render as atom" do
- get :show, project_id: project.path, id: "master.atom"
+ get :show, project_id: project.to_param, id: "master", format: "atom"
response.should be_success
response.content_type.should == 'application/atom+xml'
end
diff --git a/spec/controllers/merge_requests_controller_spec.rb b/spec/controllers/merge_requests_controller_spec.rb
index 37e36efc1ca..69708edd8b1 100644
--- a/spec/controllers/merge_requests_controller_spec.rb
+++ b/spec/controllers/merge_requests_controller_spec.rb
@@ -1,20 +1,20 @@
require 'spec_helper'
-describe MergeRequestsController do
- let(:project) { create(:project) }
+describe Projects::MergeRequestsController do
+ let(:project) { create(:project_with_code) }
let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request_with_diffs, project: project, target_branch: "bcf03b5d~3", source_branch: "bcf03b5d") }
+ let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project, target_branch: "bcf03b5d~3", source_branch: "bcf03b5d") }
before do
sign_in(user)
project.team << [user, :master]
- MergeRequestsController.any_instance.stub(validates_merge_request: true)
+ Projects::MergeRequestsController.any_instance.stub(validates_merge_request: true, )
end
describe "#show" do
- shared_examples "export as" do |format|
+ shared_examples "export merge as" do |format|
it "should generally work" do
- get :show, project_id: project.code, id: merge_request.id, format: format
+ get :show, project_id: project.to_param, id: merge_request.iid, format: format
expect(response).to be_success
end
@@ -22,19 +22,19 @@ describe MergeRequestsController do
it "should generate it" do
MergeRequest.any_instance.should_receive(:"to_#{format}")
- get :show, project_id: project.code, id: merge_request.id, format: format
+ get :show, project_id: project.to_param, id: merge_request.iid, format: format
end
it "should render it" do
- get :show, project_id: project.code, id: merge_request.id, format: format
+ get :show, project_id: project.to_param, id: merge_request.iid, format: format
- expect(response.body).to eq(merge_request.send(:"to_#{format}"))
+ expect(response.body).to eq((merge_request.send(:"to_#{format}",user)).to_s)
end
it "should not escape Html" do
MergeRequest.any_instance.stub(:"to_#{format}").and_return('HTML entities &<>" ')
- get :show, project_id: project.code, id: merge_request.id, format: format
+ get :show, project_id: project.to_param, id: merge_request.iid, format: format
expect(response.body).to_not include('&amp;')
expect(response.body).to_not include('&gt;')
@@ -44,39 +44,28 @@ describe MergeRequestsController do
end
describe "as diff" do
- include_examples "export as", :diff
+ include_examples "export merge as", :diff
let(:format) { :diff }
it "should really only be a git diff" do
- get :show, project_id: project.code, id: merge_request.id, format: format
+ get :show, project_id: project.to_param, id: merge_request.iid, format: format
expect(response.body).to start_with("diff --git")
end
end
describe "as patch" do
- include_examples "export as", :patch
+ include_examples "export merge as", :patch
let(:format) { :patch }
it "should really be a git email patch with commit" do
- get :show, project_id: project.code, id: merge_request.id, format: format
+ get :show, project_id: project.to_param, id: merge_request.iid, format: format
expect(response.body[0..100]).to start_with("From #{merge_request.commits.last.id}")
end
- # TODO: fix or remove
- #it "should contain as many patches as there are commits" do
- #get :show, project_id: project.code, id: merge_request.id, format: format
-
- #patch_count = merge_request.commits.count
- #merge_request.commits.each_with_index do |commit, patch_num|
- #expect(response.body).to match(/^From #{commit.id}/)
- #expect(response.body).to match(/^Subject: \[PATCH #{patch_num}\/#{patch_count}\]/)
- #end
- #end
-
it "should contain git diffs" do
- get :show, project_id: project.code, id: merge_request.id, format: format
+ get :show, project_id: project.to_param, id: merge_request.iid, format: format
expect(response.body).to match(/^diff --git/)
end
diff --git a/spec/controllers/tree_controller_spec.rb b/spec/controllers/tree_controller_spec.rb
index 81c7656d07a..bb1232e6264 100644
--- a/spec/controllers/tree_controller_spec.rb
+++ b/spec/controllers/tree_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe TreeController do
- let(:project) { create(:project) }
+describe Projects::TreeController do
+ let(:project) { create(:project_with_code) }
let(:user) { create(:user) }
before do
@@ -18,7 +18,7 @@ describe TreeController do
# Make sure any errors accessing the tree in our views bubble up to this spec
render_views
- before { get :show, project_id: project.code, id: id }
+ before { get :show, project_id: project.to_param, id: id }
context "valid branch, no path" do
let(:id) { 'master' }
@@ -26,17 +26,17 @@ describe TreeController do
end
context "valid branch, valid path" do
- let(:id) { 'master/README.md' }
+ let(:id) { 'master/app/' }
it { should respond_with(:success) }
end
context "valid branch, invalid path" do
- let(:id) { 'master/invalid-path.rb' }
+ let(:id) { 'master/invalid-path/' }
it { should respond_with(:not_found) }
end
context "invalid branch, valid path" do
- let(:id) { 'invalid-branch/README.md' }
+ let(:id) { 'invalid-branch/app/' }
it { should respond_with(:not_found) }
end
end
diff --git a/spec/factories.rb b/spec/factories.rb
index d2e9f48c47b..56561fe4595 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -1,3 +1,5 @@
+include ActionDispatch::TestProcess
+
FactoryGirl.define do
sequence :sentence, aliases: [:title, :content] do
Faker::Lorem.sentence
@@ -26,13 +28,49 @@ FactoryGirl.define do
factory :project do
sequence(:name) { |n| "project#{n}" }
path { name.downcase.gsub(/\s/, '_') }
+ namespace
creator
+
+ trait :source do
+ sequence(:name) { |n| "source project#{n}" }
+ end
+ trait :target do
+ sequence(:name) { |n| "target project#{n}" }
+ end
+
+ factory :source_project, traits: [:source]
+ factory :target_project, traits: [:target]
+ end
+
+
+ factory :redmine_project, parent: :project do
+ issues_tracker { "redmine" }
+ issues_tracker_id { "project_name_in_redmine" }
+ end
+
+ factory :project_with_code, parent: :project do
+ path { 'gitlabhq' }
+
+ trait :source_path do
+ path { 'source_gitlabhq' }
+ end
+
+ trait :target_path do
+ path { 'target_gitlabhq' }
+ end
+
+ factory :source_project_with_code, traits: [:source, :source_path]
+ factory :target_project_with_code, traits: [:target, :target_path]
+
+ after :create do |project|
+ TestEnv.clear_repo_dir(project.namespace, project.path)
+ TestEnv.create_repo(project.namespace, project.path)
+ end
end
factory :group do
sequence(:name) { |n| "group#{n}" }
path { name.downcase.gsub(/\s/, '_') }
- owner
type 'Group'
end
@@ -54,38 +92,51 @@ FactoryGirl.define do
project
trait :closed do
- closed true
+ state :closed
+ end
+
+ trait :reopened do
+ state :reopened
end
factory :closed_issue, traits: [:closed]
+ factory :reopened_issue, traits: [:reopened]
end
factory :merge_request do
title
author
- project
+ source_project factory: :source_project_with_code
+ target_project factory: :target_project_with_code
source_branch "master"
target_branch "stable"
- trait :closed do
- closed true
- end
-
# pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d)
trait :with_diffs do
target_branch "master" # pretend bcf03b5d~3
source_branch "stable" # pretend bcf03b5d
st_commits do
- [Commit.new(project.repo.commit('bcf03b5d')),
- Commit.new(project.repo.commit('bcf03b5d~1')),
- Commit.new(project.repo.commit('bcf03b5d~2'))]
+ [
+ source_project.repository.commit('bcf03b5d').to_hash,
+ source_project.repository.commit('bcf03b5d~1').to_hash,
+ source_project.repository.commit('bcf03b5d~2').to_hash
+ ]
end
st_diffs do
- project.repo.diff("bcf03b5d~3", "bcf03b5d")
+ source_project.repo.diff("bcf03b5d~3", "bcf03b5d")
end
end
+ trait :closed do
+ state :closed
+ end
+
+ trait :reopened do
+ state :reopened
+ end
+
factory :closed_merge_request, traits: [:closed]
+ factory :reopened_merge_request, traits: [:reopened]
factory :merge_request_with_diffs, traits: [:with_diffs]
end
@@ -99,9 +150,11 @@ 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
- commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
+ project factory: :project_with_code
+ commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
noteable_type "Commit"
end
@@ -110,14 +163,19 @@ FactoryGirl.define do
end
trait :on_merge_request do
- noteable_id 1
+ project factory: :project_with_code
+ noteable_id 1
noteable_type "MergeRequest"
end
trait :on_issue do
- noteable_id 1
+ noteable_id 1
noteable_type "Issue"
end
+
+ trait :with_attachment do
+ attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") }
+ end
end
factory :event do
@@ -135,8 +193,7 @@ FactoryGirl.define do
"ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
end
- factory :deploy_key do
- project
+ factory :deploy_key, class: 'DeployKey' do
end
factory :personal_key do
@@ -148,11 +205,23 @@ FactoryGirl.define do
"ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa ++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
end
end
+
+ factory :invalid_key do
+ key do
+ "ssh-rsa this_is_invalid_key=="
+ end
+ end
end
factory :milestone do
title
project
+
+ trait :closed do
+ state :closed
+ end
+
+ factory :closed_milestone, traits: [:closed]
end
factory :system_hook do
@@ -163,14 +232,22 @@ FactoryGirl.define do
url
end
- factory :wiki do
+ factory :project_snippet do
+ project
+ author
title
content
- user
+ file_name
+ end
+
+ factory :personal_snippet do
+ author
+ title
+ content
+ file_name
end
factory :snippet do
- project
author
title
content
@@ -193,4 +270,9 @@ FactoryGirl.define do
url
service
end
+
+ factory :deploy_keys_project do
+ deploy_key
+ project
+ end
end
diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb
new file mode 100644
index 00000000000..2f9b91acf2c
--- /dev/null
+++ b/spec/factories/forked_project_links.rb
@@ -0,0 +1,19 @@
+# == Schema Information
+#
+# Table name: forked_project_links
+#
+# id :integer not null, primary key
+# forked_to_project_id :integer not null
+# forked_from_project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+# Read about factories at https://github.com/thoughtbot/factory_girl
+
+FactoryGirl.define do
+ factory :forked_project_link do
+ association :forked_to_project, factory: :project
+ association :forked_from_project, factory: :project
+ end
+end
diff --git a/spec/factories/user_team_project_relationships.rb b/spec/factories/user_team_project_relationships.rb
deleted file mode 100644
index e900d86c2e4..00000000000
--- a/spec/factories/user_team_project_relationships.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# == Schema Information
-#
-# Table name: user_team_project_relationships
-#
-# id :integer not null, primary key
-# project_id :integer
-# user_team_id :integer
-# greatest_access :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
-FactoryGirl.define do
- factory :user_team_project_relationship do
- project
- user_team
- greatest_access { UsersProject::MASTER }
- end
-end
diff --git a/spec/factories/user_team_user_relationships.rb b/spec/factories/user_team_user_relationships.rb
deleted file mode 100644
index 8c729dd8751..00000000000
--- a/spec/factories/user_team_user_relationships.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# == Schema Information
-#
-# Table name: user_team_user_relationships
-#
-# id :integer not null, primary key
-# user_id :integer
-# user_team_id :integer
-# group_admin :boolean
-# permission :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
-FactoryGirl.define do
- factory :user_team_user_relationship do
- user
- user_team
- group_admin false
- permission { UsersProject::MASTER }
- end
-end
diff --git a/spec/factories/user_teams.rb b/spec/factories/user_teams.rb
deleted file mode 100644
index 1a9ae8e885c..00000000000
--- a/spec/factories/user_teams.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# == Schema Information
-#
-# Table name: user_teams
-#
-# id :integer not null, primary key
-# name :string(255)
-# path :string(255)
-# owner_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
-FactoryGirl.define do
- factory :user_team do
- sequence(:name) { |n| "team#{n}" }
- path { name.downcase.gsub(/\s/, '_') }
- owner
- end
-end
diff --git a/spec/factories/users_groups.rb b/spec/factories/users_groups.rb
new file mode 100644
index 00000000000..34bc0c51468
--- /dev/null
+++ b/spec/factories/users_groups.rb
@@ -0,0 +1,20 @@
+# == Schema Information
+#
+# Table name: users_groups
+#
+# id :integer not null, primary key
+# group_access :integer not null
+# group_id :integer not null
+# user_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# notification_level :integer default(3), not null
+#
+
+FactoryGirl.define do
+ factory :users_group do
+ group_access { UsersGroup::OWNER }
+ group
+ user
+ end
+end
diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb
index 5ee7354688a..66bef0761c7 100644
--- a/spec/factories_spec.rb
+++ b/spec/factories_spec.rb
@@ -1,6 +1,9 @@
require 'spec_helper'
-INVALID_FACTORIES = [:key_with_a_space_in_the_middle]
+INVALID_FACTORIES = [
+ :key_with_a_space_in_the_middle,
+ :invalid_key,
+]
FactoryGirl.factories.map(&:name).each do |factory_name|
next if INVALID_FACTORIES.include?(factory_name)
diff --git a/spec/requests/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index bc0586b2712..102a1b928f5 100644
--- a/spec/requests/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -12,7 +12,7 @@ describe "Admin::Hooks" do
describe "GET /admin/hooks" do
it "should be ok" do
visit admin_root_path
- within ".main_menu" do
+ within ".main-nav" do
click_on "Hooks"
end
current_path.should == admin_hooks_path
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
new file mode 100644
index 00000000000..23370891244
--- /dev/null
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe "Admin::Projects" do
+ before do
+ @project = create(:project)
+ login_as :admin
+ end
+
+ describe "GET /admin/projects" do
+ before do
+ visit admin_projects_path
+ end
+
+ it "should be ok" do
+ current_path.should == admin_projects_path
+ end
+
+ it "should have projects list" do
+ page.should have_content(@project.name)
+ end
+ end
+
+ describe "GET /admin/projects/:id" do
+ before do
+ visit admin_projects_path
+ click_link "#{@project.name}"
+ end
+
+ it "should have project info" do
+ page.should have_content(@project.path)
+ page.should have_content(@project.name)
+ end
+ end
+end
diff --git a/spec/requests/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 455caf4a376..8d69b595aee 100644
--- a/spec/requests/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -20,21 +20,25 @@ describe "Admin::Users" do
describe "GET /admin/users/new" do
before do
- @password = "123ABC"
visit new_admin_user_path
fill_in "user_name", with: "Big Bang"
fill_in "user_username", with: "bang"
fill_in "user_email", with: "bigbang@mail.com"
- fill_in "user_password", with: @password
- fill_in "user_password_confirmation", with: @password
end
it "should create new user" do
- expect { click_button "Save" }.to change {User.count}.by(1)
+ expect { click_button "Create user" }.to change {User.count}.by(1)
+ end
+
+ it "should apply defaults to user" do
+ click_button "Create user"
+ user = User.last
+ user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit
+ user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group
end
it "should create user with valid data" do
- click_button "Save"
+ click_button "Create user"
user = User.last
user.name.should == "Big Bang"
user.email.should == "bigbang@mail.com"
@@ -44,31 +48,18 @@ describe "Admin::Users" do
Notify.should_receive(:new_user_email)
User.observers.enable :user_observer do
- click_button "Save"
+ click_button "Create user"
end
end
it "should send valid email to user with email & password" do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(false)
User.observers.enable :user_observer do
- click_button "Save"
+ click_button "Create user"
user = User.last
email = ActionMailer::Base.deliveries.last
email.subject.should have_content("Account was created")
- email.body.should have_content(user.email)
- email.body.should have_content(@password)
- end
- end
-
- it "should send valid email to user with email without password when signup is enabled" do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
- User.observers.enable :user_observer do
- click_button "Save"
- user = User.last
- email = ActionMailer::Base.deliveries.last
- email.subject.should have_content("Account was created")
- email.body.should have_content(user.email)
- email.body.should_not have_content(@password)
+ email.text_part.body.should have_content(user.email)
+ email.text_part.body.should have_content('password')
end
end
end
@@ -82,7 +73,6 @@ describe "Admin::Users" do
it "should have user info" do
page.should have_content(@user.email)
page.should have_content(@user.name)
- page.should have_content(@user.projects_limit)
end
end
@@ -103,7 +93,7 @@ describe "Admin::Users" do
fill_in "user_name", with: "Big Bang"
fill_in "user_email", with: "bigbang@mail.com"
check "user_admin"
- click_button "Save"
+ click_button "Save changes"
end
it "should show page with new data" do
@@ -118,18 +108,4 @@ describe "Admin::Users" do
end
end
end
-
- describe "Add new project" do
- before do
- @new_project = create(:project)
- visit admin_user_path(@user)
- end
-
- it "should create new user" do
- select @new_project.name, from: "project_ids"
- expect { click_button "Add" }.to change { UsersProject.count }.by(1)
- page.should have_content @new_project.name
- current_path.should == admin_user_path(@user)
- end
- end
end
diff --git a/spec/requests/admin/security_spec.rb b/spec/features/admin/security_spec.rb
index 6306832628b..6306832628b 100644
--- a/spec/requests/admin/security_spec.rb
+++ b/spec/features/admin/security_spec.rb
diff --git a/spec/requests/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index 6f5d51d16b6..6f5d51d16b6 100644
--- a/spec/requests/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
diff --git a/spec/requests/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index 6257ad5c895..6257ad5c895 100644
--- a/spec/requests/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
diff --git a/spec/requests/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index 0488c1f2266..c9bbdad380f 100644
--- a/spec/requests/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe "Issues Feed" do
describe "GET /issues" do
let!(:user) { create(:user) }
- let!(:project) { create(:project, namespace: user.namespace) }
+ let!(:project) { create(:project) }
let!(:issue) { create(:issue, author: user, project: project) }
before { project.team << [user, :developer] }
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
new file mode 100644
index 00000000000..2ea569a6208
--- /dev/null
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -0,0 +1,129 @@
+require 'spec_helper'
+
+describe "GitLab Flavored Markdown" do
+ let(:project) { create(:project_with_code) }
+ let(:issue) { create(:issue, project: project) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:fred) do
+ u = create(:user, name: "fred")
+ project.team << [u, :master]
+ u
+ end
+
+ before do
+ Commit.any_instance.stub(title: "fix ##{issue.iid}\n\nask @#{fred.username} for details")
+ end
+
+ let(:commit) { project.repository.commit }
+
+ before do
+ login_as :user
+ project.team << [@user, :developer]
+ end
+
+ describe "for commits" do
+ it "should render title in commits#index" do
+ visit project_commits_path(project, 'master', limit: 1)
+
+ page.should have_link("##{issue.iid}")
+ end
+
+ it "should render title in commits#show" do
+ visit project_commit_path(project, commit)
+
+ page.should have_link("##{issue.iid}")
+ end
+
+ it "should render description in commits#show" do
+ visit project_commit_path(project, commit)
+
+ page.should have_link("@#{fred.username}")
+ end
+
+ it "should render title in repositories#branches" do
+ visit project_branches_path(project)
+
+ page.should have_link("##{issue.iid}")
+ end
+ end
+
+ describe "for issues" do
+ before do
+ @other_issue = create(:issue,
+ author: @user,
+ assignee: @user,
+ project: project)
+ @issue = create(:issue,
+ author: @user,
+ assignee: @user,
+ project: project,
+ title: "fix ##{@other_issue.iid}",
+ description: "ask @#{fred.username} for details")
+ end
+
+ it "should render subject in issues#index" do
+ visit project_issues_path(project)
+
+ page.should have_link("##{@other_issue.iid}")
+ end
+
+ it "should render subject in issues#show" do
+ visit project_issue_path(project, @issue)
+
+ page.should have_link("##{@other_issue.iid}")
+ end
+
+ it "should render details in issues#show" do
+ visit project_issue_path(project, @issue)
+
+ page.should have_link("@#{fred.username}")
+ end
+ end
+
+
+ describe "for merge requests" do
+ before do
+ @merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix ##{issue.iid}")
+ end
+
+ it "should render title in merge_requests#index" do
+ visit project_merge_requests_path(project)
+
+ page.should have_link("##{issue.iid}")
+ end
+
+ it "should render title in merge_requests#show" do
+ visit project_merge_request_path(project, @merge_request)
+
+ page.should have_link("##{issue.iid}")
+ end
+ end
+
+
+ describe "for milestones" do
+ before do
+ @milestone = create(:milestone,
+ project: project,
+ title: "fix ##{issue.iid}",
+ description: "ask @#{fred.username} for details")
+ end
+
+ it "should render title in milestones#index" do
+ visit project_milestones_path(project)
+
+ page.should have_link("##{issue.iid}")
+ end
+
+ it "should render title in milestones#show" do
+ visit project_milestone_path(project, @milestone)
+
+ page.should have_link("##{issue.iid}")
+ end
+
+ it "should render description in milestones#show" do
+ visit project_milestone_path(project, @milestone)
+
+ page.should have_link("@#{fred.username}")
+ end
+ end
+end
diff --git a/spec/requests/issues_spec.rb b/spec/features/issues_spec.rb
index 2e94ffd0020..0c09279e3dc 100644
--- a/spec/requests/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -45,41 +45,6 @@ describe "Issues" do
end
end
- describe "Search issue", js: true do
- before do
- ['foobar', 'foobar2', 'gitlab'].each do |title|
- create(:issue,
- author: @user,
- assignee: @user,
- project: project,
- title: title)
- end
- end
-
- it "should be able to search on different statuses" do
- issue = Issue.first # with title 'foobar'
- issue.closed = true
- issue.save
-
- visit project_issues_path(project)
- click_link 'Closed'
- fill_in 'issue_search', with: 'foobar'
-
- page.should have_content 'foobar'
- page.should_not have_content 'foobar2'
- page.should_not have_content 'gitlab'
- end
-
- it "should search for term and return the correct results" do
- visit project_issues_path(project)
- fill_in 'issue_search', with: 'foobar'
-
- page.should have_content 'foobar'
- page.should have_content 'foobar2'
- page.should_not have_content 'gitlab'
- end
- end
-
describe "Filter issue" do
before do
['foobar', 'barbaz', 'gitlab'].each do |title|
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
new file mode 100644
index 00000000000..ba580d9484d
--- /dev/null
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -0,0 +1,239 @@
+require 'spec_helper'
+
+describe "On a merge request", js: true do
+ let!(:project) { create(:project_with_code) }
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let!(:note) { create(:note_on_merge_request_with_attachment, project: project) }
+
+ before do
+ login_as :user
+ project.team << [@user, :master]
+
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ subject { page }
+
+ describe "the note form" do
+ it 'should be valid' do
+ should have_css(".js-main-target-form", visible: true, count: 1)
+ find(".js-main-target-form input[type=submit]").value.should == "Add Comment"
+ within(".js-main-target-form") { should_not have_link("Cancel") }
+ within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) }
+ end
+
+ describe "with text" do
+ before do
+ within(".js-main-target-form") do
+ fill_in "note[note]", with: "This is awesome"
+ end
+ end
+
+ it 'should have enable submit button and preview button' do
+ within(".js-main-target-form") { should_not have_css(".js-comment-button[disabled]") }
+ within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: true) }
+ end
+ end
+
+ describe "with preview" do
+ before do
+ within(".js-main-target-form") do
+ fill_in "note[note]", with: "This is awesome"
+ find(".js-note-preview-button").trigger("click")
+ end
+ end
+
+ it 'should have text and visible edit button' do
+ within(".js-main-target-form") { should have_css(".js-note-preview", text: "This is awesome", visible: true) }
+ within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) }
+ within(".js-main-target-form") { should have_css(".js-note-edit-button", visible: true) }
+ end
+ end
+ end
+
+ describe "when posting a note" do
+ before do
+ within(".js-main-target-form") do
+ fill_in "note[note]", with: "This is awsome!"
+ find(".js-note-preview-button").trigger("click")
+ click_button "Add Comment"
+ end
+ end
+
+ it 'should be added and form reset' do
+ should have_content("This is awsome!")
+ within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") }
+ within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) }
+ within(".js-main-target-form") { should have_css(".js-note-text", visible: true) }
+ end
+ end
+
+ describe "when editing a note", js: true do
+ it "should contain the hidden edit form" do
+ within("#note_#{note.id}") { should have_css(".note-edit-form", visible: false) }
+ end
+
+ describe "editing the note" do
+ before do
+ find('.note').hover
+ find(".js-note-edit").click
+ end
+
+ it "should show the note edit form and hide the note body" do
+ within("#note_#{note.id}") do
+ find(".note-edit-form", visible: true).should be_visible
+ find(".note-text", visible: false).should_not be_visible
+ end
+ end
+
+ it "should reset the edit note form textarea with the original content of the note if cancelled" do
+ find('.note').hover
+ find(".js-note-edit").click
+
+ within(".note-edit-form") do
+ fill_in "note[note]", with: "Some new content"
+ find(".btn-cancel").click
+ find(".js-note-text", visible: false).text.should == note.note
+ end
+ end
+
+ it "appends the edited at time to the note" do
+ find('.note').hover
+ find(".js-note-edit").click
+
+ within(".note-edit-form") do
+ fill_in "note[note]", with: "Some new content"
+ find(".btn-save").click
+ end
+
+ within("#note_#{note.id}") do
+ should have_css(".note-last-update small")
+ find(".note-last-update small").text.should match(/Edited just now/)
+ end
+ end
+ end
+
+ describe "deleting an attachment" do
+ before do
+ find('.note').hover
+ find(".js-note-edit").click
+ end
+
+ it "shows the delete link" do
+ within(".note-attachment") do
+ should have_css(".js-note-attachment-delete")
+ end
+ end
+
+ it "removes the attachment div and resets the edit form" do
+ find(".js-note-attachment-delete").click
+ should_not have_css(".note-attachment")
+ find(".note-edit-form", visible: false).should_not be_visible
+ end
+ end
+ end
+end
+
+describe "On a merge request diff", js: true, focus: true do
+ let!(:project) { create(:source_project_with_code) }
+ let!(:merge_request) { create(:merge_request_with_diffs, source_project: project, target_project: project) }
+
+ before do
+ login_as :user
+ project.team << [@user, :master]
+ 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_185_185"]').click
+ end
+
+ describe "the notes holder" do
+ it { should have_css(".js-temp-notes-holder") }
+
+ it { within(".js-temp-notes-holder") { should have_css(".new_note") } }
+ end
+
+ describe "the note form" do
+ it 'should be valid' do
+ within(".js-temp-notes-holder") { find("#note_noteable_type").value.should == "MergeRequest" }
+ within(".js-temp-notes-holder") { find("#note_noteable_id").value.should == merge_request.id.to_s }
+ within(".js-temp-notes-holder") { find("#note_commit_id").value.should == "" }
+ within(".js-temp-notes-holder") { find("#note_line_code").value.should == "4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185" }
+ should have_css(".js-close-discussion-note-form", text: "Cancel")
+ end
+
+ it "shouldn't add a second form for same row" do
+ find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click
+
+ should have_css("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185'] + .js-temp-notes-holder form", count: 1)
+ end
+
+ it "should be removed when canceled" do
+ within(".file form[rel$='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185']") do
+ find(".js-close-discussion-note-form").trigger("click")
+ end
+
+ should have_no_css(".js-temp-notes-holder")
+ end
+ end
+ end
+
+ describe "with muliple note forms" do
+ let!(:project) { create(:source_project_with_code) }
+ let!(:merge_request) { create(:merge_request_with_diffs, source_project: project, target_project: project) }
+
+ before do
+ find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click
+ find('a[data-line-code="342e16cbbd482ac2047dc679b2749d248cc1428f_18_17"]').click
+ end
+
+ it { should have_css(".js-temp-notes-holder", count: 2) }
+
+ describe "previewing them separately" do
+ before do
+ # add two separate texts and trigger previews on both
+ within("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185'] + .js-temp-notes-holder") do
+ fill_in "note[note]", with: "One comment on line 185"
+ 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"
+ find(".js-note-preview-button").trigger("click")
+ end
+ end
+ end
+
+ 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"
+ click_button("Add Comment")
+ end
+ end
+
+ it do
+ within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do
+ should have_no_css(".js-temp-notes-holder")
+ end
+ end
+
+ it 'should be added as discussion' do
+ should have_content("Another comment on line 17")
+ should have_css(".notes_holder")
+ should have_css(".notes_holder .note", count: 1)
+ should have_link("Reply")
+ end
+ end
+ end
+end
+
+describe "On merge request discussion", js: true do
+ describe "with merge request diff note"
+ describe "with commit note"
+ describe "with commit diff note"
+end
diff --git a/spec/requests/profile_spec.rb b/spec/features/profile_spec.rb
index c18d8f921a3..80c9f5d7f14 100644
--- a/spec/requests/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe "Profile account page" do
+ before(:each) { enable_observers }
+ after(:each) {disable_observers}
let(:user) { create(:user) }
before do
@@ -12,25 +14,12 @@ describe "Profile account page" do
Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
visit account_profile_path
end
- it { page.should have_content("Remove account") }
-
- it "should delete the account", js: true do
- expect { click_link "Delete account" }.to change {User.count}.by(-1)
- current_path.should == new_user_session_path
- end
- end
- describe "when signup is enabled and user has a project" do
- before do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
- @project = create(:project, namespace: @user.namespace)
- @project.team << [@user, :master]
- visit account_profile_path
- end
it { page.should have_content("Remove account") }
- it "should not allow user to delete the account" do
- expect { click_link "Delete account" }.not_to change {User.count}.by(-1)
+ it "should delete the account" do
+ expect { click_link "Delete account" }.to change {User.count}.by(-1)
+ current_path.should == new_user_session_path
end
end
@@ -45,4 +34,4 @@ describe "Profile account page" do
current_path.should == account_profile_path
end
end
-end \ No newline at end of file
+end
diff --git a/spec/requests/projects_spec.rb b/spec/features/projects_spec.rb
index 7bc48260935..9d5f9d5a2e2 100644
--- a/spec/requests/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe "Projects" do
+ before(:each) { enable_observers }
+ after(:each) {disable_observers}
before { login_as :user }
describe "DELETE /projects/:id" do
@@ -11,7 +13,7 @@ describe "Projects" do
end
it "should be correct path" do
- expect { click_link "Remove" }.to change {Project.count}.by(-1)
+ expect { click_link "Remove project" }.to change {Project.count}.by(-1)
end
end
end
diff --git a/spec/requests/search_spec.rb b/spec/features/search_spec.rb
index e338f359f88..3ca59da493b 100644
--- a/spec/requests/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -2,12 +2,16 @@ require 'spec_helper'
describe "Search" do
before do
+ ActiveRecord::Base.observers.enable(:user_observer)
login_as :user
- @project = create(:project)
+ @project = create(:project, namespace: @user.namespace)
@project.team << [@user, :reporter]
visit search_path
- fill_in "search", with: @project.name[0..3]
- click_button "Search"
+
+ within '.search-holder' do
+ fill_in "search", with: @project.name[0..3]
+ click_button "Search"
+ end
end
it "should show project in search results" do
diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb
new file mode 100644
index 00000000000..adec5926c6f
--- /dev/null
+++ b/spec/features/security/dashboard_access_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe "Dashboard access" do
+ describe "GET /dashboard" do
+ subject { dashboard_path }
+
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /dashboard/issues" do
+ subject { issues_dashboard_path }
+
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /dashboard/merge_requests" do
+ subject { merge_requests_dashboard_path }
+
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /dashboard/projects" do
+ subject { projects_dashboard_path }
+
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /help" do
+ subject { help_path }
+
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /projects/new" do
+ it { new_project_path.should be_allowed_for :admin }
+ it { new_project_path.should be_allowed_for :user }
+ it { new_project_path.should be_denied_for :visitor }
+ end
+
+ describe "GET /groups/new" do
+ it { new_group_path.should be_allowed_for :admin }
+ it { new_group_path.should be_allowed_for :user }
+ it { new_group_path.should be_denied_for :visitor }
+ end
+end
diff --git a/spec/features/security/group_access_spec.rb b/spec/features/security/group_access_spec.rb
new file mode 100644
index 00000000000..dea957962a8
--- /dev/null
+++ b/spec/features/security/group_access_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+describe "Group access" do
+ describe "GET /projects/new" do
+ it { new_group_path.should be_allowed_for :admin }
+ it { new_group_path.should be_allowed_for :user }
+ it { new_group_path.should be_denied_for :visitor }
+ end
+
+ describe "Group" do
+ let(:group) { create(:group) }
+
+ let(:owner) { create(:owner) }
+ let(:master) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { 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)
+ 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_denied_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_denied_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_denied_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_denied_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/requests/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb
index f854f3fb066..7754b28347a 100644
--- a/spec/requests/security/profile_access_spec.rb
+++ b/spec/features/security/profile_access_spec.rb
@@ -10,8 +10,8 @@ describe "Users Security" do
it { new_user_session_path.should_not be_404_for :visitor }
end
- describe "GET /keys" do
- subject { keys_path }
+ describe "GET /profile/keys" do
+ subject { profile_keys_path }
it { should be_allowed_for @u1 }
it { should be_allowed_for :admin }
@@ -45,5 +45,32 @@ describe "Users Security" do
it { should be_allowed_for :user }
it { should be_denied_for :visitor }
end
+
+ describe "GET /profile/history" do
+ subject { history_profile_path }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /profile/notifications" do
+ subject { profile_notifications_path }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /profile/groups" do
+ subject { profile_groups_path }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
+ end
end
end
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
new file mode 100644
index 00000000000..7f3f8c50f02
--- /dev/null
+++ b/spec/features/security/project/private_access_spec.rb
@@ -0,0 +1,218 @@
+require 'spec_helper'
+
+describe "Private Project Access" do
+ let(:project) { create(:project_with_code) }
+
+ let(:master) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:reporter) { create(:user) }
+
+ before do
+ # full access
+ project.team << [master, :master]
+
+ # readonly
+ project.team << [reporter, :reporter]
+ end
+
+ describe "GET /:project_path" do
+ subject { project_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/tree/master" do
+ subject { project_tree_path(project, project.repository.root_ref) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/commits/master" do
+ subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/commit/:sha" do
+ subject { project_commit_path(project, project.repository.commit) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/compare" do
+ subject { project_compare_index_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/team" do
+ subject { project_team_index_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/wall" do
+ subject { project_wall_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/blob" do
+ before do
+ commit = project.repository.commit
+ path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name
+ @blob_path = project_blob_path(project, File.join(commit.id, path))
+ end
+
+ it { @blob_path.should be_allowed_for master }
+ it { @blob_path.should be_allowed_for reporter }
+ it { @blob_path.should be_allowed_for :admin }
+ it { @blob_path.should be_denied_for guest }
+ it { @blob_path.should be_denied_for :user }
+ it { @blob_path.should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/edit" do
+ subject { edit_project_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/deploy_keys" do
+ subject { project_deploy_keys_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/issues" do
+ subject { project_issues_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets" do
+ subject { project_snippets_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/merge_requests" do
+ subject { project_merge_requests_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/branches/recent" do
+ subject { recent_project_branches_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/branches" do
+ subject { project_branches_path(project) }
+
+ before do
+ # Speed increase
+ Project.any_instance.stub(:branches).and_return([])
+ end
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/tags" do
+ subject { project_tags_path(project) }
+
+ before do
+ # Speed increase
+ Project.any_instance.stub(:tags).and_return([])
+ end
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/hooks" do
+ subject { project_hooks_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+end
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
new file mode 100644
index 00000000000..267643fd8ef
--- /dev/null
+++ b/spec/features/security/project/public_access_spec.rb
@@ -0,0 +1,251 @@
+require 'spec_helper'
+
+describe "Public Project Access" do
+ let(:project) { create(:project_with_code) }
+
+ let(:master) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:reporter) { create(:user) }
+
+ before do
+ # public project
+ project.public = true
+ project.save!
+
+ # full access
+ project.team << [master, :master]
+
+ # readonly
+ project.team << [reporter, :reporter]
+
+ end
+
+ describe "Project should be public" do
+ subject { project }
+
+ its(:public?) { should be_true }
+ end
+
+ describe "GET /:project_path" do
+ subject { project_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/tree/master" do
+ subject { project_tree_path(project, project.repository.root_ref) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/commits/master" do
+ subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/commit/:sha" do
+ subject { project_commit_path(project, project.repository.commit) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/compare" do
+ subject { project_compare_index_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/team" do
+ subject { project_team_index_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/wall" do
+ subject { project_wall_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/blob" do
+ before do
+ commit = project.repository.commit
+ path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name
+ @blob_path = project_blob_path(project, File.join(commit.id, path))
+ end
+
+ it { @blob_path.should be_allowed_for master }
+ it { @blob_path.should be_allowed_for reporter }
+ it { @blob_path.should be_allowed_for :admin }
+ it { @blob_path.should be_allowed_for guest }
+ it { @blob_path.should be_allowed_for :user }
+ it { @blob_path.should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/edit" do
+ subject { edit_project_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/deploy_keys" do
+ subject { project_deploy_keys_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/issues" do
+ subject { project_issues_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets" do
+ subject { project_snippets_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/snippets/new" do
+ subject { new_project_snippet_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/merge_requests" do
+ subject { project_merge_requests_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/merge_requests/new" do
+ subject { new_project_merge_request_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/branches/recent" do
+ subject { recent_project_branches_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/branches" do
+ subject { project_branches_path(project) }
+
+ before do
+ # Speed increase
+ Project.any_instance.stub(:branches).and_return([])
+ end
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/tags" do
+ subject { project_tags_path(project) }
+
+ before do
+ # Speed increase
+ Project.any_instance.stub(:tags).and_return([])
+ end
+
+ it { should be_allowed_for master }
+ it { should be_allowed_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for guest }
+ it { should be_allowed_for :user }
+ it { should be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/hooks" do
+ subject { project_hooks_path(project) }
+
+ it { should be_allowed_for master }
+ it { should be_denied_for reporter }
+ it { should be_allowed_for :admin }
+ it { should be_denied_for guest }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
+ end
+end
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
new file mode 100644
index 00000000000..ed9e44fb47e
--- /dev/null
+++ b/spec/features/users_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe 'Users' do
+ describe "GET /users/sign_up" do
+ before do
+ Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
+ end
+
+ it "should create a new user account" do
+ visit new_user_registration_path
+ fill_in "user_name", with: "Name Surname"
+ fill_in "user_username", with: "Great"
+ fill_in "user_email", with: "name@mail.com"
+ fill_in "user_password", with: "password1234"
+ fill_in "user_password_confirmation", with: "password1234"
+ expect { click_button "Sign up" }.to change {User.count}.by(1)
+ end
+ end
+end
diff --git a/spec/fixtures/dk.png b/spec/fixtures/dk.png
new file mode 100644
index 00000000000..87ce25e877a
--- /dev/null
+++ b/spec/fixtures/dk.png
Binary files differ
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index ba1af08421b..229f49659cf 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -83,4 +83,26 @@ describe ApplicationHelper do
end
end
+
+ describe "user_color_scheme_class" do
+ context "with current_user is nil" do
+ it "should return a string" do
+ stub!(:current_user).and_return(nil)
+ user_color_scheme_class.should be_kind_of(String)
+ end
+ end
+
+ context "with a current_user" do
+ (1..5).each do |color_scheme_id|
+ context "with color_scheme_id == #{color_scheme_id}" do
+ it "should return a string" do
+ current_user = double(:color_scheme_id => color_scheme_id)
+ stub!(:current_user).and_return(current_user)
+ user_color_scheme_class.should be_kind_of(String)
+ end
+ end
+ end
+ end
+ end
+
end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 1b067972c81..d49247accc2 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -2,14 +2,15 @@ require "spec_helper"
describe GitlabMarkdownHelper do
include ApplicationHelper
+ include IssuesHelper
- let!(:project) { create(:project) }
+ let!(:project) { create(:project_with_code) }
let(:user) { create(:user, username: 'gfm') }
- let(:commit) { CommitDecorator.decorate(project.repository.commit) }
+ let(:commit) { project.repository.commit }
let(:issue) { create(:issue, project: project) }
- let(:merge_request) { create(:merge_request, project: project) }
- let(:snippet) { create(:snippet, project: project) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:snippet) { create(:project_snippet, project: project) }
let(:member) { project.users_projects.where(user_id: user).first }
before do
@@ -19,7 +20,7 @@ describe GitlabMarkdownHelper do
describe "#gfm" do
it "should return unaltered text if project is nil" do
- actual = "Testing references: ##{issue.id}"
+ actual = "Testing references: ##{issue.iid}"
gfm(actual).should_not == actual
@@ -84,7 +85,7 @@ describe GitlabMarkdownHelper do
describe "referencing a team member" do
let(:actual) { "@#{user.username} you are right." }
- let(:expected) { project_team_member_path(project, member) }
+ let(:expected) { user_path(user) }
before do
project.team << [user, :master]
@@ -174,14 +175,14 @@ describe GitlabMarkdownHelper do
describe "referencing an issue" do
let(:object) { issue }
- let(:reference) { "##{issue.id}" }
+ let(:reference) { "##{issue.iid}" }
include_examples 'referenced object'
end
describe "referencing a merge request" do
let(:object) { merge_request }
- let(:reference) { "!#{merge_request.id}" }
+ let(:reference) { "!#{merge_request.iid}" }
include_examples 'referenced object'
end
@@ -189,12 +190,47 @@ describe GitlabMarkdownHelper do
describe "referencing a snippet" do
let(:object) { snippet }
let(:reference) { "$#{snippet.id}" }
+ let(:actual) { "Reference to #{reference}" }
+ let(:expected) { project_snippet_path(project, object) }
+
+ it "should link using a valid id" do
+ gfm(actual).should match(expected)
+ end
+
+ it "should link with adjacent text" do
+ # Wrap the reference in parenthesis
+ gfm(actual.gsub(reference, "(#{reference})")).should match(expected)
+
+ # Append some text to the end of the reference
+ gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected)
+ end
+
+ it "should keep whitespace intact" do
+ actual = "Referenced #{reference} already."
+ expected = /Referenced <a.+>[^\s]+<\/a> already/
+ gfm(actual).should match(expected)
+ end
+
+ it "should not link with an invalid id" do
+ # Modify the reference string so it's still parsed, but is invalid
+ reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
+ gfm(actual).should == actual
+ end
+
+ it "should include a title attribute" do
+ title = "Snippet: #{object.title}"
+ gfm(actual).should match(/title="#{title}"/)
+ end
+
+ it "should include standard gfm classes" do
+ css = object.class.to_s.underscore
+ gfm(actual).should match(/class="\s?gfm gfm-snippet\s?"/)
+ end
- include_examples 'referenced object'
end
describe "referencing multiple objects" do
- let(:actual) { "!#{merge_request.id} -> #{commit.id} -> ##{issue.id}" }
+ let(:actual) { "!#{merge_request.iid} -> #{commit.id} -> ##{issue.iid}" }
it "should link to the merge request" do
expected = project_merge_request_path(project, merge_request)
@@ -251,7 +287,7 @@ describe GitlabMarkdownHelper do
gfm(":invalid-emoji:").should_not match(/<img/)
end
- it "should work independet of reference links (i.e. without @project being set)" do
+ it "should work independent of reference links (i.e. without @project being set)" do
@project = nil
gfm(":+1:").should match(/<img/)
end
@@ -263,7 +299,7 @@ describe GitlabMarkdownHelper do
let(:issues) { create_list(:issue, 2, project: project) }
it "should handle references nested in links with all the text" do
- actual = link_to_gfm("This should finally fix ##{issues[0].id} and ##{issues[1].id} for real", commit_path)
+ actual = link_to_gfm("This should finally fix ##{issues[0].iid} and ##{issues[1].iid} for real", commit_path)
# Break the result into groups of links with their content, without
# closing tags
@@ -275,7 +311,7 @@ describe GitlabMarkdownHelper do
# First issue link
groups[1].should match(/href="#{project_issue_url(project, issues[0])}"/)
- groups[1].should match(/##{issues[0].id}$/)
+ groups[1].should match(/##{issues[0].iid}$/)
# Internal commit link
groups[2].should match(/href="#{commit_path}"/)
@@ -283,7 +319,7 @@ describe GitlabMarkdownHelper do
# Second issue link
groups[3].should match(/href="#{project_issue_url(project, issues[1])}"/)
- groups[3].should match(/##{issues[1].id}$/)
+ groups[3].should match(/##{issues[1].iid}$/)
# Trailing commit link
groups[4].should match(/href="#{commit_path}"/)
@@ -296,7 +332,7 @@ describe GitlabMarkdownHelper do
end
it "escapes HTML passed in as the body" do
- actual = "This is a <h1>test</h1> - see ##{issues[0].id}"
+ actual = "This is a <h1>test</h1> - see ##{issues[0].iid}"
link_to_gfm(actual, commit_path).should match('&lt;h1&gt;test&lt;/h1&gt;')
end
end
@@ -309,25 +345,34 @@ describe GitlabMarkdownHelper do
end
it "should handle references in headers" do
- actual = "\n# Working around ##{issue.id}\n## Apply !#{merge_request.id}"
+ actual = "\n# Working around ##{issue.iid}\n## Apply !#{merge_request.iid}"
- markdown(actual).should match(%r{<h1[^<]*>Working around <a.+>##{issue.id}</a></h1>})
- markdown(actual).should match(%r{<h2[^<]*>Apply <a.+>!#{merge_request.id}</a></h2>})
+ markdown(actual).should match(%r{<h1[^<]*>Working around <a.+>##{issue.iid}</a></h1>})
+ markdown(actual).should match(%r{<h2[^<]*>Apply <a.+>!#{merge_request.iid}</a></h2>})
end
it "should handle references in lists" do
project.team << [user, :master]
- actual = "\n* dark: ##{issue.id}\n* light by @#{member.user.username}"
+ actual = "\n* dark: ##{issue.iid}\n* light by @#{member.user.username}"
- markdown(actual).should match(%r{<li>dark: <a.+>##{issue.id}</a></li>})
+ markdown(actual).should match(%r{<li>dark: <a.+>##{issue.iid}</a></li>})
markdown(actual).should match(%r{<li>light by <a.+>@#{member.user.username}</a></li>})
end
it "should handle references in <em>" do
- actual = "Apply _!#{merge_request.id}_ ASAP"
+ actual = "Apply _!#{merge_request.iid}_ ASAP"
- markdown(actual).should match(%r{Apply <em><a.+>!#{merge_request.id}</a></em>})
+ markdown(actual).should match(%r{Apply <em><a.+>!#{merge_request.iid}</a></em>})
+ end
+
+ it "should handle tables" do
+ actual = %Q{| header 1 | header 2 |
+| -------- | -------- |
+| cell 1 | cell 2 |
+| cell 3 | cell 4 |}
+
+ markdown(actual).should match(/\A<table/)
end
it "should leave code blocks untouched" do
@@ -343,23 +388,47 @@ describe GitlabMarkdownHelper do
end
it "should leave ref-like autolinks untouched" do
- markdown("look at http://example.tld/#!#{merge_request.id}").should == "<p>look at <a href=\"http://example.tld/#!#{merge_request.id}\">http://example.tld/#!#{merge_request.id}</a></p>\n"
+ markdown("look at http://example.tld/#!#{merge_request.iid}").should == "<p>look at <a href=\"http://example.tld/#!#{merge_request.iid}\">http://example.tld/#!#{merge_request.iid}</a></p>\n"
end
it "should leave ref-like href of 'manual' links untouched" do
- markdown("why not [inspect !#{merge_request.id}](http://example.tld/#!#{merge_request.id})").should == "<p>why not <a href=\"http://example.tld/#!#{merge_request.id}\">inspect </a><a href=\"#{project_merge_request_url(project, merge_request)}\" class=\"gfm gfm-merge_request \" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.id}</a><a href=\"http://example.tld/#!#{merge_request.id}\"></a></p>\n"
+ markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})").should == "<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a href=\"#{project_merge_request_url(project, merge_request)}\" class=\"gfm gfm-merge_request \" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n"
end
it "should leave ref-like src of images untouched" do
- markdown("screen shot: ![some image](http://example.tld/#!#{merge_request.id})").should == "<p>screen shot: <img src=\"http://example.tld/#!#{merge_request.id}\" alt=\"some image\"></p>\n"
+ markdown("screen shot: ![some image](http://example.tld/#!#{merge_request.iid})").should == "<p>screen shot: <img src=\"http://example.tld/#!#{merge_request.iid}\" alt=\"some image\"></p>\n"
end
it "should generate absolute urls for refs" do
- markdown("##{issue.id}").should include(project_issue_url(project, issue))
+ markdown("##{issue.iid}").should include(project_issue_url(project, issue))
end
it "should generate absolute urls for emoji" do
markdown(":smile:").should include("src=\"#{url_to_image("emoji/smile")}")
end
end
+
+ describe "#render_wiki_content" do
+ before do
+ @wiki = stub('WikiPage')
+ @wiki.stub(:content).and_return('wiki content')
+ end
+
+ it "should use GitLab Flavored Markdown for markdown files" do
+ @wiki.stub(:format).and_return(:markdown)
+
+ helper.should_receive(:markdown).with('wiki content')
+
+ helper.render_wiki_content(@wiki)
+ end
+
+ it "should use the Gollum renderer for all other file types" do
+ @wiki.stub(:format).and_return(:rdoc)
+ formatted_content_stub = stub('formatted_content')
+ formatted_content_stub.should_receive(:html_safe)
+ @wiki.stub(:formatted_content).and_return(formatted_content_stub)
+
+ helper.render_wiki_content(@wiki)
+ end
+ end
end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
new file mode 100644
index 00000000000..3595af32431
--- /dev/null
+++ b/spec/helpers/issues_helper_spec.rb
@@ -0,0 +1,106 @@
+require "spec_helper"
+
+describe IssuesHelper do
+ let(:project) { create :project }
+ let(:issue) { create :issue, project: project }
+ let(:ext_project) { create :redmine_project }
+
+ describe :title_for_issue do
+ it "should return issue title if used internal tracker" do
+ @project = project
+ title_for_issue(issue.iid).should eq issue.title
+ end
+
+ it "should always return empty string if used external tracker" do
+ @project = ext_project
+ title_for_issue(rand(100)).should eq ""
+ end
+
+ it "should always return empty string if project nil" do
+ @project = nil
+
+ title_for_issue(rand(100)).should eq ""
+ end
+ end
+
+ describe :url_for_project_issues do
+ let(:project_url) { Gitlab.config.issues_tracker.redmine.project_url}
+ let(:ext_expected) do
+ project_url.gsub(':project_id', ext_project.id.to_s)
+ .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
+ end
+ let(:int_expected) { polymorphic_path([project]) }
+
+ it "should return internal path if used internal tracker" do
+ @project = project
+ url_for_project_issues.should match(int_expected)
+ end
+
+ it "should return path to external tracker" do
+ @project = ext_project
+
+ url_for_project_issues.should match(ext_expected)
+ end
+
+ it "should return empty string if project nil" do
+ @project = nil
+
+ url_for_project_issues.should eq ""
+ end
+ end
+
+ describe :url_for_issue do
+ let(:issue_id) { 3 }
+ let(:issues_url) { Gitlab.config.issues_tracker.redmine.issues_url}
+ let(:ext_expected) do
+ issues_url.gsub(':id', issue_id.to_s)
+ .gsub(':project_id', ext_project.id.to_s)
+ .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
+ end
+ let(:int_expected) { polymorphic_path([project, issue]) }
+
+ it "should return internal path if used internal tracker" do
+ @project = project
+ url_for_issue(issue.iid).should match(int_expected)
+ end
+
+ it "should return path to external tracker" do
+ @project = ext_project
+
+ url_for_issue(issue_id).should match(ext_expected)
+ end
+
+ it "should return empty string if project nil" do
+ @project = nil
+
+ url_for_issue(issue.iid).should eq ""
+ end
+ end
+
+ describe :url_for_new_issue do
+ let(:issues_url) { Gitlab.config.issues_tracker.redmine.new_issue_url}
+ let(:ext_expected) do
+ issues_url.gsub(':project_id', ext_project.id.to_s)
+ .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
+ end
+ let(:int_expected) { new_project_issue_path(project) }
+
+ it "should return internal path if used internal tracker" do
+ @project = project
+ url_for_new_issue.should match(int_expected)
+ end
+
+ it "should return path to external tracker" do
+ @project = ext_project
+
+ url_for_new_issue.should match(ext_expected)
+ end
+
+ it "should return empty string if project nil" do
+ @project = nil
+
+ url_for_new_issue.should eq ""
+ end
+ end
+
+end
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
new file mode 100644
index 00000000000..f97959ee8f4
--- /dev/null
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+# Specs in this file have access to a helper object that includes
+# the NotificationsHelper. For example:
+#
+# describe NotificationsHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# helper.concat_strings("this","that").should == "this that"
+# end
+# end
+# end
+describe NotificationsHelper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/app/views/admin/projects/team.html.haml b/spec/javascripts/helpers/.gitkeep
index e69de29bb2d..e69de29bb2d 100644
--- a/app/views/admin/projects/team.html.haml
+++ b/spec/javascripts/helpers/.gitkeep
diff --git a/spec/javascripts/stat_graph_contributors_graph_spec.js b/spec/javascripts/stat_graph_contributors_graph_spec.js
new file mode 100644
index 00000000000..8d2e2038a55
--- /dev/null
+++ b/spec/javascripts/stat_graph_contributors_graph_spec.js
@@ -0,0 +1,125 @@
+describe("ContributorsGraph", function () {
+ describe("#set_x_domain", function () {
+ it("set the x_domain", function () {
+ ContributorsGraph.set_x_domain(20)
+ expect(ContributorsGraph.prototype.x_domain).toEqual(20)
+ })
+ })
+
+ describe("#set_y_domain", function () {
+ it("sets the y_domain", function () {
+ ContributorsGraph.set_y_domain([{commits: 30}])
+ expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30])
+ })
+ })
+
+ describe("#init_x_domain", function () {
+ it("sets the initial x_domain", function () {
+ ContributorsGraph.init_x_domain([{date: "2013-01-31"}, {date: "2012-01-31"}])
+ expect(ContributorsGraph.prototype.x_domain).toEqual(["2012-01-31", "2013-01-31"])
+ })
+ })
+
+ describe("#init_y_domain", function () {
+ it("sets the initial y_domain", function () {
+ ContributorsGraph.init_y_domain([{commits: 30}])
+ expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30])
+ })
+ })
+
+ describe("#init_domain", function () {
+ it("calls init_x_domain and init_y_domain", function () {
+ spyOn(ContributorsGraph, "init_x_domain")
+ spyOn(ContributorsGraph, "init_y_domain")
+ ContributorsGraph.init_domain()
+ expect(ContributorsGraph.init_x_domain).toHaveBeenCalled()
+ expect(ContributorsGraph.init_y_domain).toHaveBeenCalled()
+ })
+ })
+
+ describe("#set_dates", function () {
+ it("sets the dates", function () {
+ ContributorsGraph.set_dates("2013-12-01")
+ expect(ContributorsGraph.prototype.dates).toEqual("2013-12-01")
+ })
+ })
+
+ describe("#set_x_domain", function () {
+ it("sets the instance's x domain using the prototype's x_domain", function () {
+ ContributorsGraph.prototype.x_domain = 20
+ var instance = new ContributorsGraph()
+ instance.x = d3.time.scale().range([0, 100]).clamp(true)
+ spyOn(instance.x, 'domain')
+ instance.set_x_domain()
+ expect(instance.x.domain).toHaveBeenCalledWith(20)
+ })
+ })
+
+ describe("#set_y_domain", function () {
+ it("sets the instance's y domain using the prototype's y_domain", function () {
+ ContributorsGraph.prototype.y_domain = 30
+ var instance = new ContributorsGraph()
+ instance.y = d3.scale.linear().range([100, 0]).nice()
+ spyOn(instance.y, 'domain')
+ instance.set_y_domain()
+ expect(instance.y.domain).toHaveBeenCalledWith(30)
+ })
+ })
+
+ describe("#set_domain", function () {
+ it("calls set_x_domain and set_y_domain", function () {
+ var instance = new ContributorsGraph()
+ spyOn(instance, 'set_x_domain')
+ spyOn(instance, 'set_y_domain')
+ instance.set_domain()
+ expect(instance.set_x_domain).toHaveBeenCalled()
+ expect(instance.set_y_domain).toHaveBeenCalled()
+ })
+ })
+
+ describe("#set_data", function () {
+ it("sets the data", function () {
+ var instance = new ContributorsGraph()
+ instance.set_data("20")
+ expect(instance.data).toEqual("20")
+ })
+ })
+})
+
+describe("ContributorsMasterGraph", function () {
+
+ describe("#process_dates", function () {
+ it("gets and parses dates", function () {
+ var graph = new ContributorsMasterGraph()
+ var data = 'random data here'
+ spyOn(graph, 'parse_dates')
+ spyOn(graph, 'get_dates').andReturn("get")
+ spyOn(ContributorsGraph,'set_dates').andCallThrough()
+ graph.process_dates(data)
+ expect(graph.parse_dates).toHaveBeenCalledWith(data)
+ expect(graph.get_dates).toHaveBeenCalledWith(data)
+ expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get")
+ })
+ })
+
+ describe("#get_dates", function () {
+ it("plucks the date field from data collection", function () {
+ var graph = new ContributorsMasterGraph()
+ var data = [{date: "2013-01-01"}, {date: "2012-12-15"}]
+ expect(graph.get_dates(data)).toEqual(["2013-01-01", "2012-12-15"])
+ })
+ })
+
+ describe("#parse_dates", function () {
+ it("parses the dates", function () {
+ var graph = new ContributorsMasterGraph()
+ var parseDate = d3.time.format("%Y-%m-%d").parse
+ var data = [{date: "2013-01-01"}, {date: "2012-12-15"}]
+ var correct = [{date: parseDate(data[0].date)}, {date: parseDate(data[1].date)}]
+ graph.parse_dates(data)
+ expect(data).toEqual(correct)
+ })
+ })
+
+
+})
diff --git a/spec/javascripts/stat_graph_contributors_util_spec.js b/spec/javascripts/stat_graph_contributors_util_spec.js
new file mode 100644
index 00000000000..367f0af05f8
--- /dev/null
+++ b/spec/javascripts/stat_graph_contributors_util_spec.js
@@ -0,0 +1,200 @@
+describe("ContributorsStatGraphUtil", function () {
+
+ describe("#parse_log", function () {
+ it("returns a correctly parsed log", function () {
+ var fake_log = [
+ {author: "Karlo Soriano", date: "2013-05-09", additions: 471},
+ {author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1},
+ {author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3},
+ {author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3}]
+
+ var correct_parsed_log = {
+ total: [
+ {date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
+ {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
+ by_author:
+ [
+ {
+ author: "Karlo Soriano",
+ "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
+ },
+ {
+ author: "Dmitriy Zaporozhets",
+ "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
+ }
+ ]
+ }
+ expect(ContributorsStatGraphUtil.parse_log(fake_log)).toEqual(correct_parsed_log)
+ })
+ })
+
+ describe("#store_data", function () {
+
+ var fake_entry = {author: "Karlo Soriano", date: "2013-05-09", additions: 471}
+ var fake_total = {}
+ var fake_by_author = {}
+
+ it("calls #store_commits", function () {
+ spyOn(ContributorsStatGraphUtil, 'store_commits')
+ ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.store_commits).toHaveBeenCalled()
+ })
+
+ it("calls #store_additions", function () {
+ spyOn(ContributorsStatGraphUtil, 'store_additions')
+ ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.store_additions).toHaveBeenCalled()
+ })
+
+ it("calls #store_deletions", function () {
+ spyOn(ContributorsStatGraphUtil, 'store_deletions')
+ ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.store_deletions).toHaveBeenCalled()
+ })
+
+ })
+
+ describe("#store_commits", function () {
+ var fake_total = "fake_total"
+ var fake_by_author = "fake_by_author"
+
+ it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
+ spyOn(ContributorsStatGraphUtil, 'add')
+ ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]])
+ })
+ })
+
+ describe("#add", function () {
+ it("adds 1 to current test_field in collection", function () {
+ var fake_collection = {test_field: 10}
+ ContributorsStatGraphUtil.add(fake_collection, "test_field", 1)
+ expect(fake_collection.test_field).toEqual(11)
+ })
+
+ it("inits and adds 1 if test_field in collection is not defined", function () {
+ var fake_collection = {}
+ ContributorsStatGraphUtil.add(fake_collection, "test_field", 1)
+ expect(fake_collection.test_field).toEqual(1)
+ })
+ })
+
+ describe("#store_additions", function () {
+ var fake_entry = {additions: 10}
+ var fake_total= "fake_total"
+ var fake_by_author = "fake_by_author"
+ it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
+ spyOn(ContributorsStatGraphUtil, 'add')
+ ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]])
+ })
+ })
+
+ describe("#store_deletions", function () {
+ var fake_entry = {deletions: 10}
+ var fake_total= "fake_total"
+ var fake_by_author = "fake_by_author"
+ it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
+ spyOn(ContributorsStatGraphUtil, 'add')
+ ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author)
+ expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]])
+ })
+ })
+
+ describe("#add_date", function () {
+ it("adds a date field to the collection", function () {
+ var fake_date = "2013-10-02"
+ var fake_collection = {}
+ ContributorsStatGraphUtil.add_date(fake_date, fake_collection)
+ expect(fake_collection[fake_date].date).toEqual("2013-10-02")
+ })
+ })
+
+ describe("#add_author", function () {
+ it("adds an author field to the collection", function () {
+ var fake_author = "Author"
+ var fake_collection = {}
+ ContributorsStatGraphUtil.add_author(fake_author, fake_collection)
+ expect(fake_collection[fake_author].author).toEqual("Author")
+ })
+ })
+
+ describe("#get_total_data", function () {
+ it("returns the collection sorted via specified field", function () {
+ var fake_parsed_log = {
+ total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
+ {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
+ by_author:[
+ {
+ author: "Karlo Soriano",
+ "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
+ },
+ {
+ author: "Dmitriy Zaporozhets",
+ "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
+ }
+ ]};
+ var correct_total_data = [{date: "2013-05-08", commits: 3},
+ {date: "2013-05-09", commits: 1}];
+ expect(ContributorsStatGraphUtil.get_total_data(fake_parsed_log, "commits")).toEqual(correct_total_data)
+ })
+ })
+
+ describe("#pick_field", function () {
+ it("returns the collection with only the specified field and date", function () {
+ var fake_parsed_log_total = [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
+ {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}];
+ ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits")
+ var correct_pick_field_data = [{date: "2013-05-09", commits: 1},{date: "2013-05-08", commits: 3}];
+ expect(ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits")).toEqual(correct_pick_field_data)
+ })
+ })
+
+ describe("#get_author_data", function () {
+ it("returns the log by author sorted by specified field", function () {
+ var fake_parsed_log = {
+ total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
+ {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
+ by_author:[
+ {
+ author: "Karlo Soriano",
+ "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
+ },
+ {
+ author: "Dmitriy Zaporozhets",
+ "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
+ }
+ ]}
+ var correct_author_data = [{author:"Dmitriy Zaporozhets",dates:{"2013-05-08":3},deletions:7,additions:54,"commits":3},
+ {author:"Karlo Soriano",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1}]
+ expect(ContributorsStatGraphUtil.get_author_data(fake_parsed_log, "commits")).toEqual(correct_author_data)
+ })
+ })
+
+ describe("#parse_log_entry", function () {
+ it("adds the corresponding info from the log entry to the author", function () {
+ var fake_log_entry = { author: "Karlo Soriano",
+ "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
+ }
+ var correct_parsed_log = {author:"Karlo Soriano",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1}
+ expect(ContributorsStatGraphUtil.parse_log_entry(fake_log_entry, 'commits', null)).toEqual(correct_parsed_log)
+ })
+ })
+
+ describe("#in_range", function () {
+ var date = "2013-05-09"
+ it("returns true if date_range is null", function () {
+ expect(ContributorsStatGraphUtil.in_range(date, null)).toEqual(true)
+ })
+ it("returns true if date is in range", function () {
+ var date_range = [new Date("2013-01-01"), new Date("2013-12-12")]
+ expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(true)
+ })
+ it("returns false if date is not in range", function () {
+ var date_range = [new Date("1999-12-01"), new Date("2000-12-01")]
+ expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(false)
+ })
+ })
+
+
+}) \ No newline at end of file
diff --git a/spec/javascripts/stat_graph_spec.js b/spec/javascripts/stat_graph_spec.js
new file mode 100644
index 00000000000..b8881769ac1
--- /dev/null
+++ b/spec/javascripts/stat_graph_spec.js
@@ -0,0 +1,17 @@
+describe("StatGraph", function () {
+
+ describe("#get_log", function () {
+ it("returns log", function () {
+ StatGraph.log = "test";
+ expect(StatGraph.get_log()).toBe("test");
+ });
+ });
+
+ describe("#set_log", function () {
+ it("sets the log", function () {
+ StatGraph.set_log("test");
+ expect(StatGraph.log).toBe("test");
+ })
+ })
+
+}); \ No newline at end of file
diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml
new file mode 100644
index 00000000000..9bfa261a356
--- /dev/null
+++ b/spec/javascripts/support/jasmine.yml
@@ -0,0 +1,76 @@
+# src_files
+#
+# Return an array of filepaths relative to src_dir to include before jasmine specs.
+# Default: []
+#
+# EXAMPLE:
+#
+# src_files:
+# - lib/source1.js
+# - lib/source2.js
+# - dist/**/*.js
+#
+src_files:
+ - assets/application.js
+
+# stylesheets
+#
+# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
+# Default: []
+#
+# EXAMPLE:
+#
+# stylesheets:
+# - css/style.css
+# - stylesheets/*.css
+#
+stylesheets:
+ - stylesheets/**/*.css
+
+# helpers
+#
+# Return an array of filepaths relative to spec_dir to include before jasmine specs.
+# Default: ["helpers/**/*.js"]
+#
+# EXAMPLE:
+#
+# helpers:
+# - helpers/**/*.js
+#
+helpers:
+ - helpers/**/*.js
+
+# spec_files
+#
+# Return an array of filepaths relative to spec_dir to include.
+# Default: ["**/*[sS]pec.js"]
+#
+# EXAMPLE:
+#
+# spec_files:
+# - **/*[sS]pec.js
+#
+spec_files:
+ - '**/*[sS]pec.js'
+
+# src_dir
+#
+# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
+# Default: project root
+#
+# EXAMPLE:
+#
+# src_dir: public
+#
+src_dir:
+
+# spec_dir
+#
+# Spec directory path. Your spec_files must be returned relative to this path.
+# Default: spec/javascripts
+#
+# EXAMPLE:
+#
+# spec_dir: spec/javascripts
+#
+spec_dir: spec/javascripts
diff --git a/spec/javascripts/support/jasmine_helper.rb b/spec/javascripts/support/jasmine_helper.rb
new file mode 100644
index 00000000000..986a4c16f3e
--- /dev/null
+++ b/spec/javascripts/support/jasmine_helper.rb
@@ -0,0 +1,11 @@
+#Use this file to set/override Jasmine configuration options
+#You can remove it if you don't need it.
+#This file is loaded *after* jasmine.yml is interpreted.
+#
+#Example: using a different boot file.
+#Jasmine.configure do |config|
+# @config.boot_dir = '/absolute/path/to/boot_dir'
+# @config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] }
+#end
+#
+
diff --git a/spec/lib/auth_spec.rb b/spec/lib/auth_spec.rb
index 1e03bc591b4..e05fde95731 100644
--- a/spec/lib/auth_spec.rb
+++ b/spec/lib/auth_spec.rb
@@ -3,93 +3,26 @@ require 'spec_helper'
describe Gitlab::Auth do
let(:gl_auth) { Gitlab::Auth.new }
- before do
- Gitlab.config.stub(omniauth: {})
-
- @info = mock(
- uid: '12djsak321',
- name: 'John',
- email: 'john@mail.com'
- )
- end
-
- describe :find_for_ldap_auth do
- before do
- @auth = mock(
- uid: '12djsak321',
- info: @info,
- provider: 'ldap'
- )
- end
-
- it "should find by uid & provider" do
- User.should_receive :find_by_extern_uid_and_provider
- gl_auth.find_for_ldap_auth(@auth)
- end
-
- it "should update credentials by email if missing uid" do
- user = double('User')
- User.stub find_by_extern_uid_and_provider: nil
- User.stub find_by_email: user
- user.should_receive :update_attributes
- gl_auth.find_for_ldap_auth(@auth)
- end
-
-
- it "should create from auth if user doesnot exist"do
- User.stub find_by_extern_uid_and_provider: nil
- User.stub find_by_email: nil
- gl_auth.should_receive :create_from_omniauth
- gl_auth.find_for_ldap_auth(@auth)
- end
- end
-
- describe :find_or_new_for_omniauth do
+ describe :find do
before do
- @auth = mock(
- info: @info,
- provider: 'twitter',
- uid: '12djsak321',
+ @user = create(
+ :user,
+ username: 'john',
+ password: '888777',
+ password_confirmation: '888777'
)
end
- it "should find user"do
- User.should_receive :find_by_provider_and_extern_uid
- gl_auth.should_not_receive :create_from_omniauth
- gl_auth.find_or_new_for_omniauth(@auth)
- end
-
- it "should not create user"do
- User.stub find_by_provider_and_extern_uid: nil
- gl_auth.should_not_receive :create_from_omniauth
- gl_auth.find_or_new_for_omniauth(@auth)
+ it "should find user by valid login/password" do
+ gl_auth.find('john', '888777').should == @user
end
- it "should create user if single_sing_on"do
- Gitlab.config.omniauth['allow_single_sign_on'] = true
- User.stub find_by_provider_and_extern_uid: nil
- gl_auth.should_receive :create_from_omniauth
- gl_auth.find_or_new_for_omniauth(@auth)
+ it "should not find user with invalid password" do
+ gl_auth.find('john', 'invalid').should_not == @user
end
- end
-
- describe :create_from_omniauth do
- it "should create user from LDAP" do
- @auth = mock(info: @info, provider: 'ldap')
- user = gl_auth.create_from_omniauth(@auth, true)
-
- user.should be_valid
- user.extern_uid.should == @info.uid
- user.provider.should == 'ldap'
- end
-
- it "should create user from Omniauth" do
- @auth = mock(info: @info, provider: 'twitter')
- user = gl_auth.create_from_omniauth(@auth, false)
- user.should be_valid
- user.extern_uid.should == @info.uid
- user.provider.should == 'twitter'
+ it "should not find user with invalid login and password" do
+ gl_auth.find('jon', 'invalid').should_not == @user
end
end
end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index ee20ae79809..aac72c63ea5 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -54,47 +54,5 @@ describe ExtractsPath do
extract_ref('stable/CHANGELOG').should == ['stable', 'CHANGELOG']
end
end
-
- context "with a fullpath" do
- it "extracts a valid branch" do
- extract_ref('/gitlab/gitlab-ci/tree/foo/bar/baz/CHANGELOG').should == ['foo/bar/baz', 'CHANGELOG']
- end
-
- it "extracts a valid tag" do
- extract_ref('/gitlab/gitlab-ci/tree/v2.0.0/CHANGELOG').should == ['v2.0.0', 'CHANGELOG']
- end
-
- it "extracts a valid commit SHA" do
- extract_ref('/gitlab/gitlab-ci/tree/f4b14494ef6abf3d144c28e4af0c20143383e062/CHANGELOG').should ==
- ['f4b14494ef6abf3d144c28e4af0c20143383e062', 'CHANGELOG']
- end
-
- it "extracts a timestamp" do
- extract_ref('/gitlab/gitlab-ci/tree/v2.0.0/CHANGELOG?_=12354435').should == ['v2.0.0', 'CHANGELOG']
- end
- end
-
- context "with a fullpath and a relative_url_root" do
- before do
- Gitlab.config.gitlab.stub(relative_url_root: '/relative')
- end
-
- it "extracts a valid branch with relative_url_root" do
- extract_ref('/relative/gitlab/gitlab-ci/tree/foo/bar/baz/CHANGELOG').should == ['foo/bar/baz', 'CHANGELOG']
- end
-
- it "extracts a valid tag" do
- extract_ref('/relative/gitlab/gitlab-ci/tree/v2.0.0/CHANGELOG').should == ['v2.0.0', 'CHANGELOG']
- end
-
- it "extracts a valid commit SHA" do
- extract_ref('/relative/gitlab/gitlab-ci/tree/f4b14494ef6abf3d144c28e4af0c20143383e062/CHANGELOG').should ==
- ['f4b14494ef6abf3d144c28e4af0c20143383e062', 'CHANGELOG']
- end
-
- it "extracts a timestamp" do
- extract_ref('/relative/gitlab/gitlab-ci/tree/v2.0.0/CHANGELOG?_=12354435').should == ['v2.0.0', 'CHANGELOG']
- end
- end
end
end
diff --git a/spec/lib/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index 3c04f4bbeb6..f00ec0fa401 100644
--- a/spec/lib/shell_spec.rb
+++ b/spec/lib/gitlab/backend/shell_spec.rb
@@ -12,6 +12,7 @@ describe Gitlab::Shell do
it { should respond_to :remove_key }
it { should respond_to :add_repository }
it { should respond_to :remove_repository }
+ it { should respond_to :fork_repository }
it { gitlab_shell.url_to_repo('diaspora').should == Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git" }
end
diff --git a/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb b/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb
new file mode 100644
index 00000000000..b1c583c0476
--- /dev/null
+++ b/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe Gitlab::LDAP do
+ let(:gl_auth) { Gitlab::LDAP::User }
+
+ before do
+ Gitlab.config.stub(omniauth: {})
+
+ @info = mock(
+ uid: '12djsak321',
+ name: 'John',
+ email: 'john@mail.com'
+ )
+ end
+
+ describe :find_for_ldap_auth do
+ before do
+ @auth = mock(
+ uid: '12djsak321',
+ info: @info,
+ provider: 'ldap'
+ )
+ end
+
+ it "should update credentials by email if missing uid" do
+ user = double('User')
+ User.stub find_by_extern_uid_and_provider: nil
+ User.stub find_by_email: user
+ user.should_receive :update_attributes
+ gl_auth.find_or_create(@auth)
+ end
+
+ it "should update credentials by username if missing uid and Gitlab.config.ldap.allow_username_or_email_login is true" do
+ user = double('User')
+ value = Gitlab.config.ldap.allow_username_or_email_login
+ Gitlab.config.ldap['allow_username_or_email_login'] = true
+ User.stub find_by_extern_uid_and_provider: nil
+ User.stub find_by_email: nil
+ User.stub find_by_username: user
+ user.should_receive :update_attributes
+ gl_auth.find_or_create(@auth)
+ Gitlab.config.ldap['allow_username_or_email_login'] = value
+ end
+
+ it "should not update credentials by username if missing uid and Gitlab.config.ldap.allow_username_or_email_login is false" do
+ user = double('User')
+ value = Gitlab.config.ldap.allow_username_or_email_login
+ Gitlab.config.ldap['allow_username_or_email_login'] = false
+ User.stub find_by_extern_uid_and_provider: nil
+ User.stub find_by_email: nil
+ User.stub find_by_username: user
+ user.should_not_receive :update_attributes
+ gl_auth.find_or_create(@auth)
+ Gitlab.config.ldap['allow_username_or_email_login'] = value
+ end
+ end
+end
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
new file mode 100644
index 00000000000..4791be41613
--- /dev/null
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe 'Gitlab::Popen', no_db: true do
+ let (:path) { Rails.root.join('tmp').to_s }
+
+ before do
+ @klass = Class.new(Object)
+ @klass.send(:include, Gitlab::Popen)
+ end
+
+ context 'zero status' do
+ before do
+ @output, @status = @klass.new.popen('ls', path)
+ end
+
+ it { @status.should be_zero }
+ it { @output.should include('cache') }
+ end
+
+ context 'non-zero status' do
+ before do
+ @output, @status = @klass.new.popen('cat NOTHING', path)
+ end
+
+ it { @status.should == 1 }
+ it { @output.should include('No such file or directory') }
+ end
+end
+
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
new file mode 100644
index 00000000000..7d805f8c72a
--- /dev/null
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -0,0 +1,95 @@
+require 'spec_helper'
+
+describe Gitlab::ReferenceExtractor do
+ it 'extracts username references' do
+ subject.analyze "this contains a @user reference"
+ subject.users.should == ["user"]
+ end
+
+ it 'extracts issue references' do
+ subject.analyze "this one talks about issue #1234"
+ subject.issues.should == ["1234"]
+ end
+
+ it 'extracts merge request references' do
+ subject.analyze "and here's !43, a merge request"
+ subject.merge_requests.should == ["43"]
+ end
+
+ it 'extracts snippet ids' do
+ subject.analyze "snippets like $12 get extracted as well"
+ subject.snippets.should == ["12"]
+ end
+
+ it 'extracts commit shas' do
+ subject.analyze "commit shas 98cf0ae3 are pulled out as Strings"
+ subject.commits.should == ["98cf0ae3"]
+ end
+
+ it 'extracts multiple references and preserves their order' do
+ subject.analyze "@me and @you both care about this"
+ subject.users.should == ["me", "you"]
+ end
+
+ it 'leaves the original note unmodified' do
+ text = "issue #123 is just the worst, @user"
+ subject.analyze text
+ text.should == "issue #123 is just the worst, @user"
+ end
+
+ it 'handles all possible kinds of references' do
+ accessors = Gitlab::Markdown::TYPES.map { |t| "#{t}s".to_sym }
+ subject.should respond_to(*accessors)
+ end
+
+ context 'with a project' do
+ let(:project) { create(:project_with_code) }
+
+ it 'accesses valid user objects on the project team' do
+ @u_foo = create(:user, username: 'foo')
+ @u_bar = create(:user, username: 'bar')
+ create(:user, username: 'offteam')
+
+ project.team << [@u_foo, :reporter]
+ project.team << [@u_bar, :guest]
+
+ subject.analyze "@foo, @baduser, @bar, and @offteam"
+ subject.users_for(project).should == [@u_foo, @u_bar]
+ end
+
+ it 'accesses valid issue objects' do
+ @i0 = create(:issue, project: project)
+ @i1 = create(:issue, project: project)
+
+ subject.analyze "##{@i0.iid}, ##{@i1.iid}, and #999."
+ subject.issues_for(project).should == [@i0, @i1]
+ end
+
+ it 'accesses valid merge requests' do
+ @m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa')
+ @m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb')
+
+ subject.analyze "!999, !#{@m1.iid}, and !#{@m0.iid}."
+ subject.merge_requests_for(project).should == [@m1, @m0]
+ end
+
+ it 'accesses valid snippets' do
+ @s0 = create(:project_snippet, project: project)
+ @s1 = create(:project_snippet, project: project)
+ @s2 = create(:project_snippet)
+
+ subject.analyze "$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}"
+ subject.snippets_for(project).should == [@s0, @s1]
+ end
+
+ it 'accesses valid commits' do
+ commit = project.repository.commit("master")
+
+ subject.analyze "this references commits #{commit.sha[0..6]} and 012345"
+ extracted = subject.commits_for(project)
+ extracted.should have(1).item
+ extracted[0].sha.should == commit.sha
+ extracted[0].message.should == commit.message
+ end
+ end
+end
diff --git a/spec/lib/gitlab/satellite/action_spec.rb b/spec/lib/gitlab/satellite/action_spec.rb
new file mode 100644
index 00000000000..5e0a825c3c3
--- /dev/null
+++ b/spec/lib/gitlab/satellite/action_spec.rb
@@ -0,0 +1,116 @@
+require 'spec_helper'
+
+describe 'Gitlab::Satellite::Action' do
+ let(:project) { create(:project_with_code) }
+ let(:user) { create(:user) }
+
+ describe '#prepare_satellite!' do
+
+ it 'create a repository with a parking branch and one remote: origin' do
+ repo = project.satellite.repo
+
+ #now lets dirty it up
+
+ starting_remote_count = repo.git.list_remotes.size
+ starting_remote_count.should >= 1
+ #kind of hookey way to add a second remote
+ origin_uri = repo.git.remote({v: true}).split(" ")[1]
+ begin
+ repo.git.remote({raise: true}, 'add', 'another-remote', origin_uri)
+ repo.git.branch({raise: true}, 'a-new-branch')
+
+ repo.heads.size.should > (starting_remote_count)
+ repo.git.remote().split(" ").size.should > (starting_remote_count)
+ rescue
+ end
+
+ repo.git.config({}, "user.name", "#{user.name} -- foo")
+ repo.git.config({}, "user.email", "#{user.email} -- foo")
+ repo.config['user.name'].should =="#{user.name} -- foo"
+ repo.config['user.email'].should =="#{user.email} -- foo"
+
+
+ #These must happen in the context of the satellite directory...
+ satellite_action = Gitlab::Satellite::Action.new(user, project)
+ project.satellite.lock {
+ #Now clean it up, use send to get around prepare_satellite! being protected
+ satellite_action.send(:prepare_satellite!, repo)
+ }
+
+ #verify it's clean
+ heads = repo.heads.map(&:name)
+ heads.size.should == 1
+ heads.include?(Gitlab::Satellite::Satellite::PARKING_BRANCH).should == true
+ remotes = repo.git.remote().split(' ')
+ remotes.size.should == 1
+ remotes.include?('origin').should == true
+ repo.config['user.name'].should ==user.name
+ repo.config['user.email'].should ==user.email
+ end
+ end
+
+ describe '#in_locked_and_timed_satellite' do
+
+ it 'should make use of a lockfile' do
+ repo = project.satellite.repo
+ called = false
+
+ #set assumptions
+ File.rm(project.satellite.lock_file) unless !File.exists? project.satellite.lock_file
+
+ File.exists?(project.satellite.lock_file).should be_false
+
+ satellite_action = Gitlab::Satellite::Action.new(user, project)
+ satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
+ repo.should == sat_repo
+ (File.exists? project.satellite.lock_file).should be_true
+ called = true
+ end
+
+ called.should be_true
+
+ end
+
+ it 'should be able to use the satellite after locking' do
+ repo = project.satellite.repo
+ called = false
+
+ # Set base assumptions
+ if File.exists? project.satellite.lock_file
+ FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false
+ end
+
+ satellite_action = Gitlab::Satellite::Action.new(user, project)
+ satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
+ called = true
+ repo.should == sat_repo
+ (File.exists? project.satellite.lock_file).should be_true
+ FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_true
+ end
+
+ called.should be_true
+ FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false
+
+ end
+
+ class FileLockStatusChecker < File
+ def flocked? &block
+ status = flock LOCK_EX|LOCK_NB
+ case status
+ when false
+ return true
+ when 0
+ begin
+ block ? block.call : false
+ ensure
+ flock LOCK_UN
+ end
+ else
+ raise SystemCallError, status
+ end
+ end
+ end
+
+ end
+end
+
diff --git a/spec/lib/gitlab/satellite/merge_action_spec.rb b/spec/lib/gitlab/satellite/merge_action_spec.rb
new file mode 100644
index 00000000000..3be14383e06
--- /dev/null
+++ b/spec/lib/gitlab/satellite/merge_action_spec.rb
@@ -0,0 +1,148 @@
+require 'spec_helper'
+
+describe 'Gitlab::Satellite::MergeAction' do
+ before(:each) do
+# TestEnv.init(mailer: false, init_repos: true, repos: true)
+ @master = ['master', 'bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a']
+ @one_after_stable = ['stable', '6ea87c47f0f8a24ae031c3fff17bc913889ecd00'] #this commit sha is one after stable
+ @wiki_branch = ['wiki', '635d3e09b72232b6e92a38de6cc184147e5bcb41'] #this is the commit sha where the wiki branch goes off from master
+ @conflicting_metior = ['metior', '313d96e42b313a0af5ab50fa233bf43e27118b3f'] #this branch conflicts with the wiki branch
+
+ #these commits are quite close together, itended to make string diffs/format patches small
+ @close_commit1 = ['2_3_notes_fix', '8470d70da67355c9c009e4401746b1d5410af2e3']
+ @close_commit2 = ['scss_refactoring', 'f0f14c8eaba69ebddd766498a9d0b0e79becd633']
+ end
+
+ let(:project) { create(:project_with_code) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:merge_request_fork) { create(:merge_request) }
+ describe '#commits_between' do
+ def verify_commits(commits, first_commit_sha, last_commit_sha)
+ commits.each { |commit| commit.class.should == Gitlab::Git::Commit }
+ commits.first.id.should == first_commit_sha
+ commits.last.id.should == last_commit_sha
+ end
+
+ context 'on fork' do
+ it 'should get proper commits between' do
+ merge_request_fork.target_branch = @one_after_stable[0]
+ merge_request_fork.source_branch = @master[0]
+ commits = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).commits_between
+ verify_commits(commits, @one_after_stable[1], @master[1])
+
+ merge_request_fork.target_branch = @wiki_branch[0]
+ merge_request_fork.source_branch = @master[0]
+ commits = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).commits_between
+ verify_commits(commits, @wiki_branch[1], @master[1])
+ end
+ end
+
+ context 'between branches' do
+ it 'should raise exception -- not expected to be used by non forks' do
+ merge_request.target_branch = @one_after_stable[0]
+ merge_request.source_branch = @master[0]
+ expect {Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between}.to raise_error
+
+ merge_request.target_branch = @wiki_branch[0]
+ merge_request.source_branch = @master[0]
+ expect {Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between}.to raise_error
+ end
+ end
+ end
+
+ describe '#format_patch' do
+ let(:target_commit) {['artiom-config-examples','9edbac5ac88ffa1ec9dad0097226b51e29ebc9ac']}
+ let(:source_commit) {['metior', '313d96e42b313a0af5ab50fa233bf43e27118b3f']}
+
+ def verify_content(patch)
+ (patch.include? source_commit[1]).should be_true
+ (patch.include? '635d3e09b72232b6e92a38de6cc184147e5bcb41').should be_true
+ (patch.include? '2bb2dee057327c81978ed0aa99904bd7ff5e6105').should be_true
+ (patch.include? '2e83de1924ad3429b812d17498b009a8b924795d').should be_true
+ (patch.include? 'ee45a49c57a362305431cbf004e4590b713c910e').should be_true
+ (patch.include? 'a6870dd08f8f274d9a6b899f638c0c26fefaa690').should be_true
+
+ (patch.include? 'e74fae147abc7d2ffbf93d363dbbe45b87751f6f').should be_false
+ (patch.include? '86f76b11c670425bbab465087f25172378d76147').should be_false
+ end
+
+ context 'on fork' do
+ it 'should build a format patch' do
+ merge_request_fork.target_branch = target_commit[0]
+ merge_request_fork.source_branch = source_commit[0]
+ patch = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).format_patch
+ verify_content(patch)
+ end
+ end
+
+ context 'between branches' do
+ it 'should build a format patch' do
+ merge_request.target_branch = target_commit[0]
+ merge_request.source_branch = source_commit[0]
+ patch = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request).format_patch
+ verify_content(patch)
+ end
+ end
+ end
+
+ describe '#diffs_between_satellite tested against diff_in_satellite' do
+
+ def is_a_matching_diff(diff, diffs)
+ diff_count = diff.scan('diff --git').size
+ diff_count.should >= 1
+ diffs.size.should == diff_count
+ diffs.each do |a_diff|
+ a_diff.class.should == Gitlab::Git::Diff
+ (diff.include? a_diff.diff).should be_true
+ end
+ end
+
+ context 'on fork' do
+ it 'should get proper diffs' do
+ merge_request_fork.target_branch = @close_commit1[0]
+ merge_request_fork.source_branch = @master[0]
+ diffs = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).diffs_between_satellite
+
+ merge_request_fork.target_branch = @close_commit1[0]
+ merge_request_fork.source_branch = @master[0]
+ diff = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request_fork).diff_in_satellite
+
+ is_a_matching_diff(diff, diffs)
+ end
+ end
+
+ context 'between branches' do
+ it 'should get proper diffs' do
+ merge_request.target_branch = @close_commit1[0]
+ merge_request.source_branch = @master[0]
+ expect{Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite}.to raise_error
+ end
+ end
+ end
+
+ describe '#can_be_merged?' do
+ context 'on fork' do
+ it 'return true or false depending on if something is mergable' do
+ merge_request_fork.target_branch = @one_after_stable[0]
+ merge_request_fork.source_branch = @master[0]
+ Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).can_be_merged?.should be_true
+
+ merge_request_fork.target_branch = @conflicting_metior[0]
+ merge_request_fork.source_branch = @wiki_branch[0]
+ Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).can_be_merged?.should be_false
+ end
+ end
+
+ context 'between branches' do
+ it 'return true or false depending on if something is mergable' do
+ merge_request.target_branch = @one_after_stable[0]
+ merge_request.source_branch = @master[0]
+ Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).can_be_merged?.should be_true
+
+ merge_request.target_branch = @conflicting_metior[0]
+ merge_request.source_branch = @wiki_branch[0]
+ Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).can_be_merged?.should be_false
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
new file mode 100644
index 00000000000..94dccf7a4e5
--- /dev/null
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe 'Gitlab::VersionInfo', no_db: true do
+ before do
+ @unknown = Gitlab::VersionInfo.new
+ @v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1)
+ @v0_1_0 = Gitlab::VersionInfo.new(0, 1, 0)
+ @v1_0_0 = Gitlab::VersionInfo.new(1, 0, 0)
+ @v1_0_1 = Gitlab::VersionInfo.new(1, 0, 1)
+ @v1_1_0 = Gitlab::VersionInfo.new(1, 1, 0)
+ @v2_0_0 = Gitlab::VersionInfo.new(2, 0, 0)
+ end
+
+ context '>' do
+ it { @v2_0_0.should > @v1_1_0 }
+ it { @v1_1_0.should > @v1_0_1 }
+ it { @v1_0_1.should > @v1_0_0 }
+ it { @v1_0_0.should > @v0_1_0 }
+ it { @v0_1_0.should > @v0_0_1 }
+ end
+
+ context '>=' do
+ it { @v2_0_0.should >= Gitlab::VersionInfo.new(2, 0, 0) }
+ it { @v2_0_0.should >= @v1_1_0 }
+ end
+
+ context '<' do
+ it { @v0_0_1.should < @v0_1_0 }
+ it { @v0_1_0.should < @v1_0_0 }
+ it { @v1_0_0.should < @v1_0_1 }
+ it { @v1_0_1.should < @v1_1_0 }
+ it { @v1_1_0.should < @v2_0_0 }
+ end
+
+ context '<=' do
+ it { @v0_0_1.should <= Gitlab::VersionInfo.new(0, 0, 1) }
+ it { @v0_0_1.should <= @v0_1_0 }
+ end
+
+ context '==' do
+ it { @v0_0_1.should == Gitlab::VersionInfo.new(0, 0, 1) }
+ it { @v0_1_0.should == Gitlab::VersionInfo.new(0, 1, 0) }
+ it { @v1_0_0.should == Gitlab::VersionInfo.new(1, 0, 0) }
+ end
+
+ context '!=' do
+ it { @v0_0_1.should_not == @v0_1_0 }
+ end
+
+ context 'unknown' do
+ it { @unknown.should_not be @v0_0_1 }
+ it { @unknown.should_not be Gitlab::VersionInfo.new }
+ it { expect{@unknown > @v0_0_1}.to raise_error(ArgumentError) }
+ it { expect{@unknown < @v0_0_1}.to raise_error(ArgumentError) }
+ end
+
+ context 'parse' do
+ it { Gitlab::VersionInfo.parse("1.0.0").should == @v1_0_0 }
+ it { Gitlab::VersionInfo.parse("1.0.0.1").should == @v1_0_0 }
+ it { Gitlab::VersionInfo.parse("git 1.0.0b1").should == @v1_0_0 }
+ it { Gitlab::VersionInfo.parse("git 1.0b1").should_not be_valid }
+ end
+
+ context 'to_s' do
+ it { @v1_0_0.to_s.should == "1.0.0" }
+ it { @unknown.to_s.should == "Unknown" }
+ end
+end
+
diff --git a/spec/lib/oauth_spec.rb b/spec/lib/oauth_spec.rb
new file mode 100644
index 00000000000..e21074554b6
--- /dev/null
+++ b/spec/lib/oauth_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe Gitlab::OAuth::User do
+ let(:gl_auth) { Gitlab::OAuth::User }
+
+ before do
+ Gitlab.config.stub(omniauth: {})
+
+ @info = mock(
+ uid: '12djsak321',
+ name: 'John',
+ email: 'john@mail.com'
+ )
+ end
+
+ describe :create do
+ it "should create user from LDAP" do
+ @auth = mock(info: @info, provider: 'ldap')
+ user = gl_auth.create(@auth)
+
+ user.should be_valid
+ user.extern_uid.should == @info.uid
+ user.provider.should == 'ldap'
+ end
+
+ it "should create user from Omniauth" do
+ @auth = mock(info: @info, provider: 'twitter')
+ user = gl_auth.create(@auth)
+
+ user.should be_valid
+ user.extern_uid.should == @info.uid
+ user.provider.should == 'twitter'
+ end
+
+ it "should apply defaults to user" do
+ @auth = mock(info: @info, provider: 'ldap')
+ user = gl_auth.create(@auth)
+
+ user.should be_valid
+ user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit
+ user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group
+ end
+ end
+end
diff --git a/spec/lib/project_mover_spec.rb b/spec/lib/project_mover_spec.rb
deleted file mode 100644
index 9202befdcb2..00000000000
--- a/spec/lib/project_mover_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::ProjectMover do
- let(:base_path) { Rails.root.join('tmp', 'rspec-sandbox') }
-
- before do
- FileUtils.rm_rf base_path if File.exists? base_path
- FileUtils.mkdir_p base_path
-
- Gitlab.config.gitlab_shell.stub(repos_path: base_path)
-
- @project = create(:project)
- end
-
- after do
- FileUtils.rm_rf base_path
- end
-
- it "should move project to subdir" do
- mk_dir base_path, '', @project.path
- mover = Gitlab::ProjectMover.new(@project, '', 'opensource')
-
- mover.execute.should be_true
- moved?('opensource', @project.path).should be_true
- end
-
- it "should move project from one subdir to another" do
- mk_dir base_path, 'vsizov', @project.path
- mover = Gitlab::ProjectMover.new(@project, 'vsizov', 'randx')
-
- mover.execute.should be_true
- moved?('randx', @project.path).should be_true
- end
-
- it "should move project from subdir to base" do
- mk_dir base_path, 'vsizov', @project.path
- mover = Gitlab::ProjectMover.new(@project, 'vsizov', '')
-
- mover.execute.should be_true
- moved?('', @project.path).should be_true
- end
-
- it "should raise if destination exists" do
- mk_dir base_path, '', @project.path
- mk_dir base_path, 'vsizov', @project.path
- mover = Gitlab::ProjectMover.new(@project, 'vsizov', '')
-
- expect { mover.execute }.to raise_error(Gitlab::ProjectMover::ProjectMoveError)
- end
-
- it "should raise if move failed" do
- mk_dir base_path
- mover = Gitlab::ProjectMover.new(@project, 'vsizov', '')
-
- expect { mover.execute }.to raise_error(Gitlab::ProjectMover::ProjectMoveError)
- end
-
-
- def mk_dir base_path, namespace = '', project_path = ''
- FileUtils.mkdir_p File.join(base_path, namespace, project_path + ".git")
- end
-
- def moved? namespace, path
- File.exists?(File.join(base_path, namespace, path + '.git'))
- end
-end
diff --git a/spec/lib/votes_spec.rb b/spec/lib/votes_spec.rb
index b49ed15b5c3..a3c353d5eab 100644
--- a/spec/lib/votes_spec.rb
+++ b/spec/lib/votes_spec.rb
@@ -1,132 +1,136 @@
require 'spec_helper'
-describe MergeRequest do
- let(:merge_request) { FactoryGirl.create(:merge_request_with_diffs) }
+describe Issue, 'Votes' do
+ let(:issue) { create(:issue) }
describe "#upvotes" do
it "with no notes has a 0/0 score" do
- merge_request.upvotes.should == 0
+ issue.upvotes.should == 0
end
it "should recognize non-+1 notes" do
- merge_request.notes << create(:note, note: "No +1 here")
- merge_request.should have(1).note
- merge_request.notes.first.upvote?.should be_false
- merge_request.upvotes.should == 0
+ add_note "No +1 here"
+ issue.should have(1).note
+ issue.notes.first.upvote?.should be_false
+ issue.upvotes.should == 0
end
it "should recognize a single +1 note" do
- merge_request.notes << create(:note, note: "+1 This is awesome")
- merge_request.upvotes.should == 1
+ add_note "+1 This is awesome"
+ issue.upvotes.should == 1
end
it "should recognize multiple +1 notes" do
- merge_request.notes << create(:note, note: "+1 This is awesome")
- merge_request.notes << create(:note, note: "+1 I want this")
- merge_request.upvotes.should == 2
+ add_note "+1 This is awesome"
+ add_note "+1 I want this"
+ issue.upvotes.should == 2
end
end
describe "#downvotes" do
it "with no notes has a 0/0 score" do
- merge_request.downvotes.should == 0
+ issue.downvotes.should == 0
end
it "should recognize non--1 notes" do
- merge_request.notes << create(:note, note: "Almost got a -1")
- merge_request.should have(1).note
- merge_request.notes.first.downvote?.should be_false
- merge_request.downvotes.should == 0
+ add_note "Almost got a -1"
+ issue.should have(1).note
+ issue.notes.first.downvote?.should be_false
+ issue.downvotes.should == 0
end
it "should recognize a single -1 note" do
- merge_request.notes << create(:note, note: "-1 This is bad")
- merge_request.downvotes.should == 1
+ add_note "-1 This is bad"
+ issue.downvotes.should == 1
end
it "should recognize multiple -1 notes" do
- merge_request.notes << create(:note, note: "-1 This is bad")
- merge_request.notes << create(:note, note: "-1 Away with this")
- merge_request.downvotes.should == 2
+ add_note "-1 This is bad"
+ add_note "-1 Away with this"
+ issue.downvotes.should == 2
end
end
describe "#votes_count" do
it "with no notes has a 0/0 score" do
- merge_request.votes_count.should == 0
+ issue.votes_count.should == 0
end
it "should recognize non notes" do
- merge_request.notes << create(:note, note: "No +1 here")
- merge_request.should have(1).note
- merge_request.votes_count.should == 0
+ add_note "No +1 here"
+ issue.should have(1).note
+ issue.votes_count.should == 0
end
it "should recognize a single +1 note" do
- merge_request.notes << create(:note, note: "+1 This is awesome")
- merge_request.votes_count.should == 1
+ add_note "+1 This is awesome"
+ issue.votes_count.should == 1
end
it "should recognize a single -1 note" do
- merge_request.notes << create(:note, note: "-1 This is bad")
- merge_request.votes_count.should == 1
+ add_note "-1 This is bad"
+ issue.votes_count.should == 1
end
it "should recognize multiple notes" do
- merge_request.notes << create(:note, note: "+1 This is awesome")
- merge_request.notes << create(:note, note: "-1 This is bad")
- merge_request.notes << create(:note, note: "+1 I want this")
- merge_request.votes_count.should == 3
+ add_note "+1 This is awesome"
+ add_note "-1 This is bad"
+ add_note "+1 I want this"
+ issue.votes_count.should == 3
end
end
describe "#upvotes_in_percent" do
it "with no notes has a 0% score" do
- merge_request.upvotes_in_percent.should == 0
+ issue.upvotes_in_percent.should == 0
end
it "should count a single 1 note as 100%" do
- merge_request.notes << create(:note, note: "+1 This is awesome")
- merge_request.upvotes_in_percent.should == 100
+ add_note "+1 This is awesome"
+ issue.upvotes_in_percent.should == 100
end
it "should count multiple +1 notes as 100%" do
- merge_request.notes << create(:note, note: "+1 This is awesome")
- merge_request.notes << create(:note, note: "+1 I want this")
- merge_request.upvotes_in_percent.should == 100
+ add_note "+1 This is awesome"
+ add_note "+1 I want this"
+ issue.upvotes_in_percent.should == 100
end
it "should count fractions for multiple +1 and -1 notes correctly" do
- merge_request.notes << create(:note, note: "+1 This is awesome")
- merge_request.notes << create(:note, note: "+1 I want this")
- merge_request.notes << create(:note, note: "-1 This is bad")
- merge_request.notes << create(:note, note: "+1 me too")
- merge_request.upvotes_in_percent.should == 75
+ add_note "+1 This is awesome"
+ add_note "+1 I want this"
+ add_note "-1 This is bad"
+ add_note "+1 me too"
+ issue.upvotes_in_percent.should == 75
end
end
describe "#downvotes_in_percent" do
it "with no notes has a 0% score" do
- merge_request.downvotes_in_percent.should == 0
+ issue.downvotes_in_percent.should == 0
end
it "should count a single -1 note as 100%" do
- merge_request.notes << create(:note, note: "-1 This is bad")
- merge_request.downvotes_in_percent.should == 100
+ add_note "-1 This is bad"
+ issue.downvotes_in_percent.should == 100
end
it "should count multiple -1 notes as 100%" do
- merge_request.notes << create(:note, note: "-1 This is bad")
- merge_request.notes << create(:note, note: "-1 Away with this")
- merge_request.downvotes_in_percent.should == 100
+ add_note "-1 This is bad"
+ add_note "-1 Away with this"
+ issue.downvotes_in_percent.should == 100
end
it "should count fractions for multiple +1 and -1 notes correctly" do
- merge_request.notes << create(:note, note: "+1 This is awesome")
- merge_request.notes << create(:note, note: "+1 I want this")
- merge_request.notes << create(:note, note: "-1 This is bad")
- merge_request.notes << create(:note, note: "+1 me too")
- merge_request.downvotes_in_percent.should == 25
+ add_note "+1 This is awesome"
+ add_note "+1 I want this"
+ add_note "-1 This is bad"
+ add_note "+1 me too"
+ issue.downvotes_in_percent.should == 25
end
end
+
+ def add_note(text)
+ issue.notes << create(:note, note: text, project: issue.project)
+ end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index befc10594db..0787bdbea6f 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -5,7 +5,7 @@ describe Notify do
include EmailSpec::Matchers
let(:recipient) { create(:user, email: 'recipient@example.com') }
- let(:project) { create(:project) }
+ let(:project) { create(:project_with_code) }
shared_examples 'a multiple recipients email' do
it 'is sent to the given recipient' do
@@ -15,7 +15,7 @@ describe Notify do
describe 'for new users, the email' do
let(:example_site_path) { root_path }
- let(:new_user) { create(:user, email: 'newguy@example.com') }
+ let(:new_user) { create(:user, email: 'newguy@example.com', created_by_id: 1) }
subject { Notify.new_user_email(new_user.id, new_user.password) }
@@ -32,8 +32,7 @@ describe Notify do
end
it 'contains the new user\'s password' do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(false)
- should have_body_text /#{new_user.password}/
+ should have_body_text /password/
end
it 'includes a link to the site' do
@@ -61,8 +60,7 @@ describe Notify do
end
it 'should not contain the new user\'s password' do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
- should_not have_body_text /#{new_user.password}/
+ should_not have_body_text /password/
end
it 'includes a link to the site' do
@@ -70,6 +68,28 @@ describe Notify do
end
end
+ describe 'user added ssh key' do
+ let(:key) { create(:personal_key) }
+
+ subject { Notify.new_ssh_key_email(key.id) }
+
+ 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
+ end
+
+ it 'contains the new ssh key title' do
+ should have_body_text /#{key.title}/
+ end
+
+ it 'includes a link to ssh keys page' do
+ should have_body_text /#{profile_keys_path}/
+ end
+ end
+
context 'for a project' do
describe 'items that are assignable, the email' do
let(:assignee) { create(:user, email: 'assignee@example.com') }
@@ -85,12 +105,12 @@ describe Notify do
let(:issue) { create(:issue, assignee: assignee, project: project ) }
describe 'that are new' do
- subject { Notify.new_issue_email(issue.id) }
+ subject { Notify.new_issue_email(issue.assignee_id, issue.id) }
it_behaves_like 'an assignee email'
it 'has the correct subject' do
- should have_subject /#{project.name} \| new issue ##{issue.id} \| #{issue.title}/
+ should have_subject /#{project.name} \| new issue ##{issue.iid} \| #{issue.title}/
end
it 'contains a link to the new issue' do
@@ -106,7 +126,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it 'has the correct subject' do
- should have_subject /changed issue ##{issue.id} \| #{issue.title}/
+ should have_subject /changed issue ##{issue.iid} \| #{issue.title}/
end
it 'contains the name of the previous assignee' do
@@ -128,7 +148,7 @@ describe Notify do
subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) }
it 'has the correct subject' do
- should have_subject /changed issue ##{issue.id} \| #{issue.title}/i
+ should have_subject /changed issue ##{issue.iid} \| #{issue.title}/i
end
it 'contains the new status' do
@@ -147,15 +167,15 @@ describe Notify do
end
context 'for merge requests' do
- let(:merge_request) { create(:merge_request, assignee: assignee, project: project) }
+ let(:merge_request) { create(:merge_request, assignee: assignee, source_project: project, target_project: project) }
describe 'that are new' do
- subject { Notify.new_merge_request_email(merge_request.id) }
+ subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
it_behaves_like 'an assignee email'
it 'has the correct subject' do
- should have_subject /new merge request !#{merge_request.id}/
+ should have_subject /new merge request !#{merge_request.iid}/
end
it 'contains a link to the new merge request' do
@@ -179,7 +199,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it 'has the correct subject' do
- should have_subject /changed merge request !#{merge_request.id}/
+ should have_subject /changed merge request !#{merge_request.iid}/
end
it 'contains the name of the previous assignee' do
@@ -198,6 +218,24 @@ describe Notify do
end
end
+ describe 'project was moved' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ subject { Notify.project_was_moved_email(project.id, user.id) }
+
+ it 'has the correct subject' do
+ should have_subject /project was moved/
+ end
+
+ it 'contains name of project' do
+ should have_body_text /#{project.name_with_namespace}/
+ end
+
+ it 'contains new user role' do
+ should have_body_text /#{project.ssh_url_to_repo}/
+ end
+ end
+
describe 'project access changed' do
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -212,7 +250,7 @@ describe Notify do
should have_body_text /#{project.name}/
end
it 'contains new user role' do
- should have_body_text /#{users_project.project_access_human}/
+ should have_body_text /#{users_project.human_access}/
end
end
@@ -239,7 +277,7 @@ describe Notify do
end
describe 'on a project wall' do
- let(:note_on_the_wall_path) { wall_project_path(project, anchor: "note_#{note.id}") }
+ let(:note_on_the_wall_path) { project_wall_path(project, anchor: "note_#{note.id}") }
subject { Notify.note_wall_email(recipient.id, note.id) }
@@ -255,14 +293,7 @@ describe Notify do
end
describe 'on a commit' do
- let(:commit) do
- mock(:commit).tap do |commit|
- commit.stub(:id).and_return('fauxsha1')
- commit.stub(:project).and_return(project)
- commit.stub(:short_id).and_return('fauxsha1')
- commit.stub(:safe_message).and_return('some message')
- end
- end
+ let(:commit) { project.repository.commit }
before(:each) { note.stub(:noteable).and_return(commit) }
@@ -275,12 +306,12 @@ describe Notify do
end
it 'contains a link to the commit' do
- should have_body_text /fauxsha1/
+ should have_body_text commit.short_id
end
end
describe 'on a merge request' do
- let(:merge_request) { create(:merge_request, project: project) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") }
before(:each) { note.stub(:noteable).and_return(merge_request) }
@@ -289,7 +320,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.id}/
+ should have_subject /note for merge request !#{merge_request.iid}/
end
it 'contains a link to the merge request note' do
@@ -307,7 +338,7 @@ describe Notify do
it_behaves_like 'a note email'
it 'has the correct subject' do
- should have_subject /note for issue ##{issue.id}/
+ should have_subject /note for issue ##{issue.iid}/
end
it 'contains a link to the issue note' do
@@ -316,4 +347,24 @@ describe Notify do
end
end
end
+
+ describe 'group access changed' do
+ let(:group) { create(:group) }
+ let(:user) { create(:user) }
+ let(:membership) { create(:users_group, group: group, user: user) }
+
+ subject { Notify.group_access_granted_email(membership.id) }
+
+ it 'has the correct subject' do
+ should have_subject /access to group was granted/
+ end
+
+ it 'contains name of project' do
+ should have_body_text /#{group.name}/
+ end
+
+ it 'contains new user role' do
+ should have_body_text /#{membership.human_access}/
+ end
+ end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 91301029e89..fa556f94a1d 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -1,83 +1,35 @@
require 'spec_helper'
describe Commit do
- let(:commit) { create(:project).repository.commit }
+ let(:project) { create :project_with_code }
+ let(:commit) { project.repository.commit }
- describe CommitDecorator do
- let(:decorator) { CommitDecorator.new(commit) }
-
- describe '#title' do
- it "returns no_commit_message when safe_message is blank" do
- decorator.stub(:safe_message).and_return('')
- decorator.title.should == "--no commit message"
- end
-
- it "truncates a message without a newline at 70 characters" do
- message = commit.safe_message * 10
-
- decorator.stub(:safe_message).and_return(message)
- decorator.title.should == "#{message[0..69]}&hellip;"
- end
-
- it "truncates a message with a newline before 80 characters at the newline" do
- message = commit.safe_message.split(" ").first
-
- decorator.stub(:safe_message).and_return(message + "\n" + message)
- decorator.title.should == message
- end
-
- it "truncates a message with a newline after 80 characters at 70 characters" do
- message = (commit.safe_message * 10) + "\n"
-
- decorator.stub(:safe_message).and_return(message)
- decorator.title.should == "#{message[0..69]}&hellip;"
- end
+ describe '#title' do
+ it "returns no_commit_message when safe_message is blank" do
+ commit.stub(:safe_message).and_return('')
+ commit.title.should == "--no commit message"
end
- end
- describe "Commit info" do
- before do
- @committer = double(
- email: 'mike@smith.com',
- name: 'Mike Smith'
- )
+ it "truncates a message without a newline at 80 characters" do
+ message = commit.safe_message * 10
- @author = double(
- email: 'john@smith.com',
- name: 'John Smith'
- )
+ commit.stub(:safe_message).and_return(message)
+ commit.title.should == "#{message[0..79]}&hellip;"
+ end
- @raw_commit = double(
- id: "bcf03b5de6abcf03b5de6c",
- author: @author,
- committer: @committer,
- committed_date: Date.yesterday,
- message: 'Refactoring specs'
- )
+ it "truncates a message with a newline before 80 characters at the newline" do
+ message = commit.safe_message.split(" ").first
- @commit = Commit.new(@raw_commit)
+ commit.stub(:safe_message).and_return(message + "\n" + message)
+ commit.title.should == message
end
- it { @commit.short_id.should == "bcf03b5de6a" }
- it { @commit.safe_message.should == @raw_commit.message }
- it { @commit.created_at.should == @raw_commit.committed_date }
- it { @commit.author_email.should == @author.email }
- it { @commit.author_name.should == @author.name }
- it { @commit.committer_name.should == @committer.name }
- it { @commit.committer_email.should == @committer.email }
- it { @commit.different_committer?.should be_true }
- end
+ it "truncates a message with a newline after 80 characters at 70 characters" do
+ message = (commit.safe_message * 10) + "\n"
- describe "Class methods" do
- subject { Commit }
-
- it { should respond_to(:find_or_first) }
- it { should respond_to(:fresh_commits) }
- it { should respond_to(:commits_with_refs) }
- it { should respond_to(:commits_since) }
- it { should respond_to(:commits_between) }
- it { should respond_to(:commits) }
- it { should respond_to(:compare) }
+ commit.stub(:safe_message).and_return(message)
+ commit.title.should == "#{message[0..79]}&hellip;"
+ end
end
describe "delegation" do
@@ -86,13 +38,32 @@ describe Commit do
it { should respond_to(:message) }
it { should respond_to(:authored_date) }
it { should respond_to(:committed_date) }
+ it { should respond_to(:committer_email) }
+ it { should respond_to(:author_email) }
it { should respond_to(:parents) }
it { should respond_to(:date) }
- it { should respond_to(:committer) }
- it { should respond_to(:author) }
it { should respond_to(:diffs) }
it { should respond_to(:tree) }
it { should respond_to(:id) }
it { should respond_to(:to_patch) }
end
+
+ describe '#closes_issues' do
+ let(:issue) { create :issue, project: project }
+
+ it 'detects issues that this commit is marked as closing' do
+ commit.stub(issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/, safe_message: "Fixes ##{issue.iid}")
+ commit.closes_issues(project).should == [issue]
+ end
+ end
+
+ it_behaves_like 'a mentionable' do
+ let(:subject) { commit }
+ let(:mauthor) { create :user, email: commit.author_email }
+ let(:backref_text) { "commit #{subject.sha[0..5]}" }
+ let(:set_mentionable_text) { ->(txt){ subject.stub(safe_message: txt) } }
+
+ # Include the subject in the repository stub.
+ let(:extra_commits) { [subject] }
+ end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index b5d4bd7b406..852146ebaec 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -11,11 +11,12 @@ describe Issue, "Issuable" do
end
describe "Validation" do
+ before { subject.stub(set_iid: false) }
it { should validate_presence_of(:project) }
+ it { should validate_presence_of(:iid) }
it { should validate_presence_of(:author) }
it { should validate_presence_of(:title) }
it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) }
- it { should ensure_inclusion_of(:closed).in_array([true, false]) }
end
describe "Scope" do
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
new file mode 100644
index 00000000000..b76ca660411
--- /dev/null
+++ b/spec/models/deploy_key_spec.rb
@@ -0,0 +1,25 @@
+# == Schema Information
+#
+# Table name: keys
+#
+# id :integer not null, primary key
+# user_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# key :text
+# title :string(255)
+# type :string(255)
+# fingerprint :string(255)
+#
+
+require 'spec_helper'
+
+describe DeployKey do
+ let(:project) { create(:project) }
+ let(:deploy_key) { create(:deploy_key, projects: [project]) }
+
+ describe "Associations" do
+ it { should have_many(:deploy_keys_projects) }
+ it { should have_many(:projects) }
+ end
+end
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
new file mode 100644
index 00000000000..aeec1713558
--- /dev/null
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -0,0 +1,24 @@
+# == Schema Information
+#
+# Table name: deploy_keys_projects
+#
+# id :integer not null, primary key
+# deploy_key_id :integer not null
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+require 'spec_helper'
+
+describe DeployKeysProject do
+ describe "Associations" do
+ it { should belong_to(:deploy_key) }
+ it { should belong_to(:project) }
+ end
+
+ describe "Validation" do
+ it { should validate_presence_of(:project_id) }
+ it { should validate_presence_of(:deploy_key_id) }
+ end
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index cc78993905f..85bdf08ae64 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -72,6 +72,7 @@ describe Event do
before {
Event.should_receive :create
+ observer.stub(notification: stub.as_null_object)
}
describe "Joined project team" do
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
new file mode 100644
index 00000000000..44b8c6155be
--- /dev/null
+++ b/spec/models/forked_project_link_spec.rb
@@ -0,0 +1,67 @@
+# == Schema Information
+#
+# Table name: forked_project_links
+#
+# id :integer not null, primary key
+# forked_to_project_id :integer not null
+# forked_from_project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+require 'spec_helper'
+
+describe ForkedProjectLink, "add link on fork" do
+ let(:project_from) { create(:project) }
+ let(:namespace) { create(:namespace) }
+ let(:user) { create(:user, namespace: namespace) }
+
+ before do
+ @project_to = fork_project(project_from, user)
+ end
+
+ it "project_to should know it is forked" do
+ @project_to.forked?.should be_true
+ end
+
+ it "project should know who it is forked from" do
+ @project_to.forked_from_project.should == project_from
+ end
+end
+
+describe :forked_from_project do
+ let(:forked_project_link) { build(:forked_project_link) }
+ let(:project_from) { create(:project) }
+ let(:project_to) { create(:project, forked_project_link: forked_project_link) }
+
+
+ before :each do
+ forked_project_link.forked_from_project = project_from
+ forked_project_link.forked_to_project = project_to
+ forked_project_link.save!
+ end
+
+
+ it "project_to should know it is forked" do
+ project_to.forked?.should be_true
+ end
+
+ it "project_from should not be forked" do
+ project_from.forked?.should be_false
+ end
+
+ it "project_to.destroy should destroy fork_link" do
+ forked_project_link.should_receive(:destroy)
+ project_to.destroy
+ end
+
+end
+
+def fork_project(from_project, user)
+ context = Projects::ForkContext.new(from_project, user)
+ shell = mock("gitlab_shell")
+ shell.stub(fork_repository: true)
+ context.stub(gitlab_shell: shell)
+ context.execute
+end
+
diff --git a/spec/models/gitlab_ci_service_spec.rb b/spec/models/gitlab_ci_service_spec.rb
index b86588af1ac..56efa9df457 100644
--- a/spec/models/gitlab_ci_service_spec.rb
+++ b/spec/models/gitlab_ci_service_spec.rb
@@ -11,6 +11,8 @@
# updated_at :datetime not null
# active :boolean default(FALSE), not null
# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
#
require 'spec_helper'
diff --git a/spec/models/gollum_wiki_spec.rb b/spec/models/gollum_wiki_spec.rb
new file mode 100644
index 00000000000..aa850dfd0a3
--- /dev/null
+++ b/spec/models/gollum_wiki_spec.rb
@@ -0,0 +1,196 @@
+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
+
+ def commit_details
+ commit = {name: user.name, email: user.email, message: "test commit"}
+ end
+
+ def create_page(name, content)
+ subject.wiki.write_page(name, :markdown, content, commit_details)
+ end
+
+ def destroy_page(page)
+ subject.wiki.delete_page(page, commit_details)
+ end
+
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:user) { project.owner }
+ let(:gitlab_shell) { Gitlab::Shell.new }
+
+ subject { GollumWiki.new(project, user) }
+
+ before do
+ create_temp_repo(subject.send(:path_to_repo))
+ end
+
+ describe "#path_with_namespace" do
+ it "returns the project path with namespace with the .wiki extension" do
+ subject.path_with_namespace.should == project.path_with_namespace + ".wiki"
+ end
+ end
+
+ describe "#url_to_repo" do
+ it "returns the correct ssh url to the repo" do
+ subject.url_to_repo.should == gitlab_shell.url_to_repo(subject.path_with_namespace)
+ end
+ end
+
+ describe "#ssh_url_to_repo" do
+ it "equals #url_to_repo" do
+ subject.ssh_url_to_repo.should == subject.url_to_repo
+ end
+ end
+
+ describe "#http_url_to_repo" do
+ it "provides the full http url to the repo" do
+ gitlab_url = Gitlab.config.gitlab.url
+ repo_http_url = "#{gitlab_url}/#{subject.path_with_namespace}.git"
+ subject.http_url_to_repo.should == repo_http_url
+ end
+ end
+
+ describe "#wiki" do
+ it "contains a Gollum::Wiki instance" do
+ subject.wiki.should be_a Gollum::Wiki
+ end
+
+ before do
+ Gitlab::Shell.any_instance.stub(:add_repository) do
+ create_temp_repo("#{Rails.root}/tmp/test-git-base-path/non-existant.wiki.git")
+ end
+ project.stub(:path_with_namespace).and_return("non-existant")
+ end
+
+ it "creates a new wiki repo if one does not yet exist" do
+ wiki = GollumWiki.new(project, user)
+ wiki.create_page("index", "test content").should_not == false
+
+ FileUtils.rm_rf wiki.send(:path_to_repo)
+ end
+
+ it "raises CouldNotCreateWikiError if it can't create the wiki repository" do
+ GollumWiki.any_instance.stub(:init_repo).and_return(false)
+ expect { GollumWiki.new(project, user).wiki }.to raise_exception(GollumWiki::CouldNotCreateWikiError)
+ end
+ end
+
+ describe "#pages" do
+ before do
+ create_page("index", "This is an awesome new Gollum Wiki")
+ @pages = subject.pages
+ end
+
+ after do
+ destroy_page(@pages.first.page)
+ end
+
+ it "returns an array of WikiPage instances" do
+ @pages.first.should be_a WikiPage
+ end
+
+ it "returns the correct number of pages" do
+ @pages.count.should == 1
+ end
+ end
+
+ describe "#find_page" do
+ before do
+ create_page("index page", "This is an awesome Gollum Wiki")
+ end
+
+ after do
+ destroy_page(subject.pages.first.page)
+ end
+
+ it "returns the latest version of the page if it exists" do
+ page = subject.find_page("index page")
+ page.title.should == "index page"
+ end
+
+ it "returns nil if the page does not exist" do
+ subject.find_page("non-existant").should == nil
+ end
+
+ it "can find a page by slug" do
+ page = subject.find_page("index-page")
+ page.title.should == "index page"
+ end
+
+ it "returns a WikiPage instance" do
+ page = subject.find_page("index page")
+ page.should be_a WikiPage
+ end
+ end
+
+ describe "#create_page" do
+ after do
+ destroy_page(subject.pages.first.page)
+ end
+
+ it "creates a new wiki page" do
+ subject.create_page("test page", "this is content").should_not == false
+ subject.pages.count.should == 1
+ end
+
+ it "returns false when a duplicate page exists" do
+ subject.create_page("test page", "content")
+ subject.create_page("test page", "content").should == false
+ end
+
+ it "stores an error message when a duplicate page exists" do
+ 2.times { subject.create_page("test page", "content") }
+ subject.error_message.should =~ /Duplicate page:/
+ end
+
+ it "sets the correct commit message" do
+ subject.create_page("test page", "some content", :markdown, "commit message")
+ subject.pages.first.page.version.message.should == "commit message"
+ end
+ end
+
+ describe "#update_page" do
+ before do
+ create_page("update-page", "some content")
+ @gollum_page = subject.wiki.paged("update-page")
+ subject.update_page(@gollum_page, "some other content", :markdown, "updated page")
+ @page = subject.pages.first.page
+ end
+
+ after do
+ destroy_page(@page)
+ end
+
+ it "updates the content of the page" do
+ @page.raw_data.should == "some other content"
+ end
+
+ it "sets the correct commit message" do
+ @page.version.message.should == "updated page"
+ end
+ end
+
+ describe "#delete_page" do
+ before do
+ create_page("index", "some content")
+ @page = subject.wiki.paged("index")
+ end
+
+ it "deletes the page" do
+ subject.delete_page(@page)
+ subject.pages.count.should == 0
+ end
+ end
+
+end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 108bc303540..ce1aa05bcd7 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -2,13 +2,14 @@
#
# Table name: namespaces
#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# type :string(255)
+# id :integer not null, primary key
+# name :string(255) not null
+# path :string(255) not null
+# owner_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# type :string(255)
+# description :string(255) default(""), not null
#
require 'spec_helper'
@@ -16,18 +17,29 @@ require 'spec_helper'
describe Group do
let!(:group) { create(:group) }
- it { should have_many :projects }
+ describe "Associations" do
+ it { should have_many :projects }
+ it { should have_many :users_groups }
+ end
+
it { should validate_presence_of :name }
it { should validate_uniqueness_of(:name) }
it { should validate_presence_of :path }
it { should validate_uniqueness_of(:path) }
- it { should validate_presence_of :owner }
+ it { should_not validate_presence_of :owner }
describe :users do
- it { group.users.should == [group.owner] }
+ it { group.users.should == group.owners }
end
describe :human_name do
it { group.human_name.should == group.name }
end
+
+ describe :add_users do
+ let(:user) { create(:user) }
+ before { group.add_user(user, UsersGroup::MASTER) }
+
+ it { group.users_groups.masters.map(&:user).should include(user) }
+ end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 10db53e0745..75155d5dc1d 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -9,11 +9,12 @@
# project_id :integer
# created_at :datetime not null
# updated_at :datetime not null
-# closed :boolean default(FALSE), not null
# position :integer default(0)
# branch_name :string(255)
# description :text
# milestone_id :integer
+# state :string(255)
+# iid :integer
#
require 'spec_helper'
@@ -44,34 +45,21 @@ describe Issue do
end
end
- describe '#is_being_closed?' do
- it 'returns true if the closed attribute has changed and is now true' do
- subject.closed = true
- subject.is_being_closed?.should be_true
- end
- it 'returns false if the closed attribute has changed and is now false' do
- issue = create(:closed_issue)
- issue.closed = false
- issue.is_being_closed?.should be_false
- end
- it 'returns false if the closed attribute has not changed' do
- subject.is_being_closed?.should be_false
- end
- end
+ describe '#is_being_reassigned?' do
+ it 'returns issues assigned to user' do
+ user = create :user
+ 2.times do
+ issue = create :issue, assignee: user
+ end
- describe '#is_being_reopened?' do
- it 'returns true if the closed attribute has changed and is now false' do
- issue = create(:closed_issue)
- issue.closed = false
- issue.is_being_reopened?.should be_true
- end
- it 'returns false if the closed attribute has changed and is now true' do
- subject.closed = true
- subject.is_being_reopened?.should be_false
- end
- it 'returns false if the closed attribute has not changed' do
- subject.is_being_reopened?.should be_false
+ Issue.open_for(user).count.should eq 2
end
end
+
+ it_behaves_like 'an editable mentionable' do
+ let(:subject) { create :issue, project: mproject }
+ let(:backref_text) { "issue ##{subject.iid}" }
+ let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
+ end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 94b952cf932..9c872c02a53 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -2,14 +2,14 @@
#
# Table name: keys
#
-# id :integer not null, primary key
-# user_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-# key :text
-# title :string(255)
-# identifier :string(255)
-# project_id :integer
+# id :integer not null, primary key
+# user_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# key :text
+# title :string(255)
+# type :string(255)
+# fingerprint :string(255)
#
require 'spec_helper'
@@ -17,7 +17,6 @@ require 'spec_helper'
describe Key do
describe "Associations" do
it { should belong_to(:user) }
- it { should belong_to(:project) }
end
describe "Mass assignment" do
@@ -37,44 +36,36 @@ describe Key do
end
context "validation of uniqueness" do
+ let(:user) { create(:user) }
- context "as a deploy key" do
- let!(:deploy_key) { create(:deploy_key) }
-
- it "does not accept the same key twice for a project" do
- key = build(:key, project: deploy_key.project)
- key.should_not be_valid
- end
-
- it "does not accept the same key for another project" do
- key = build(:key, project_id: 0)
- key.should_not be_valid
- end
+ it "accepts the key once" do
+ build(:key, user: user).should be_valid
end
- context "as a personal key" do
- let(:user) { create(:user) }
-
- it "accepts the key once" do
- build(:key, user: user).should be_valid
- end
+ it "does not accept the exact same key twice" do
+ create(:key, user: user)
+ build(:key, user: user).should_not be_valid
+ end
- it "does not accepts the key twice" do
- create(:key, user: user)
- build(:key, user: user).should_not be_valid
- end
+ it "does not accept a duplicate key with a different comment" do
+ create(:key, user: user)
+ duplicate = build(:key, user: user)
+ duplicate.key << ' extra comment'
+ duplicate.should_not be_valid
end
end
context "validate it is a fingerprintable key" do
- let(:user) { create(:user) }
-
it "accepts the fingerprintable key" do
- build(:key, user: user).should be_valid
+ build(:key).should be_valid
end
- it "rejects the unfingerprintable key" do
+ it "rejects the unfingerprintable key (contains space in middle)" do
build(:key_with_a_space_in_the_middle).should_not be_valid
end
+
+ it "rejects the unfingerprintable key (not a key)" do
+ build(:invalid_key).should_not be_valid
+ end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 41f4ede5d89..18c32d4fb74 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -2,21 +2,22 @@
#
# Table name: merge_requests
#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# closed :boolean default(FALSE), not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# st_commits :text(2147483647)
-# st_diffs :text(2147483647)
-# merged :boolean default(FALSE), not null
-# state :integer default(1), not null
-# milestone_id :integer
+# id :integer not null, primary key
+# target_branch :string(255) not null
+# source_branch :string(255) not null
+# source_project_id :integer not null
+# author_id :integer
+# assignee_id :integer
+# title :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+# st_commits :text(2147483647)
+# st_diffs :text(2147483647)
+# milestone_id :integer
+# state :string(255)
+# merge_status :string(255)
+# target_project_id :integer not null
+# iid :integer
#
require 'spec_helper'
@@ -32,6 +33,12 @@ describe MergeRequest do
it { should_not allow_mass_assignment_of(:project_id) }
end
+ describe "Respond to" do
+ it { should respond_to(:unchecked?) }
+ it { should respond_to(:can_be_merged?) }
+ it { should respond_to(:cannot_be_merged?) }
+ end
+
describe 'modules' do
it { should include_module(Issuable) }
end
@@ -40,7 +47,7 @@ describe MergeRequest do
let!(:merge_request) { create(:merge_request) }
before do
- merge_request.stub(:commits) { [merge_request.project.repository.commit] }
+ merge_request.stub(:commits) { [merge_request.source_project.repository.commit] }
create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit')
create(:note, noteable: merge_request)
end
@@ -63,34 +70,67 @@ describe MergeRequest do
end
end
- describe '#is_being_closed?' do
- it 'returns true if the closed attribute has changed and is now true' do
- subject.closed = true
- subject.is_being_closed?.should be_true
+ describe '#for_fork?' do
+ it 'returns true if the merge request is for a fork' do
+ subject.source_project = create(:source_project)
+ subject.target_project = create(:target_project)
+
+ subject.for_fork?.should be_true
+ end
+ it 'returns false if is not for a fork' do
+ subject.source_project = create(:source_project)
+ subject.target_project = subject.source_project
+ subject.for_fork?.should be_false
+ end
+ end
+
+ describe '#allow_source_branch_removal?' do
+ it 'should not allow removal when mr is a fork' do
+
+ subject.disallow_source_branch_removal?.should be_true
end
- it 'returns false if the closed attribute has changed and is now false' do
- merge_request = create(:closed_merge_request)
- merge_request.closed = false
- merge_request.is_being_closed?.should be_false
+ it 'should not allow removal when the mr is not a fork, but the source branch is the root reference' do
+ subject.target_project = subject.source_project
+ subject.source_branch = subject.source_project.repository.root_ref
+ subject.disallow_source_branch_removal?.should be_true
end
- it 'returns false if the closed attribute has not changed' do
- subject.is_being_closed?.should be_false
+
+ it 'should not disallow removal when the mr is not a fork, and but source branch is not the root reference' do
+ subject.target_project = subject.source_project
+ subject.source_branch = "Something Different #{subject.source_project.repository.root_ref}"
+ subject.for_fork?.should be_false
+ subject.disallow_source_branch_removal?.should be_false
end
end
+ describe 'detection of issues to be closed' do
+ let(:issue0) { create :issue, project: subject.project }
+ let(:issue1) { create :issue, project: subject.project }
+ let(:commit0) { mock('commit0', closes_issues: [issue0]) }
+ let(:commit1) { mock('commit1', closes_issues: [issue0]) }
+ let(:commit2) { mock('commit2', closes_issues: [issue1]) }
- describe '#is_being_reopened?' do
- it 'returns true if the closed attribute has changed and is now false' do
- merge_request = create(:closed_merge_request)
- merge_request.closed = false
- merge_request.is_being_reopened?.should be_true
+ before do
+ subject.stub(unmerged_commits: [commit0, commit1, commit2])
end
- it 'returns false if the closed attribute has changed and is now true' do
- subject.closed = true
- subject.is_being_reopened?.should be_false
+
+ it 'accesses the set of issues that will be closed on acceptance' do
+ subject.project.default_branch = subject.target_branch
+
+ subject.closes_issues.should == [issue0, issue1].sort_by(&:id)
end
- it 'returns false if the closed attribute has not changed' do
- subject.is_being_reopened?.should be_false
+
+ it 'only lists issues as to be closed if it targets the default branch' do
+ subject.project.default_branch = 'master'
+ subject.target_branch = 'something-else'
+
+ subject.closes_issues.should be_empty
end
end
+
+ it_behaves_like 'an editable mentionable' do
+ let(:subject) { create :merge_request, source_project: mproject, target_project: mproject }
+ let(:backref_text) { "merge request !#{subject.iid}" }
+ let(:set_mentionable_text) { ->(txt){ subject.title = txt } }
+ end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 2ea2c56a6f7..b41012a3b8c 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -7,9 +7,10 @@
# project_id :integer not null
# description :text
# due_date :date
-# closed :boolean default(FALSE), not null
# created_at :datetime not null
# updated_at :datetime not null
+# state :string(255)
+# iid :integer
#
require 'spec_helper'
@@ -25,9 +26,9 @@ describe Milestone do
end
describe "Validation" do
+ before { subject.stub(set_iid: false) }
it { should validate_presence_of(:title) }
it { should validate_presence_of(:project) }
- it { should ensure_inclusion_of(:closed).in_array([true, false]) }
end
let(:milestone) { create(:milestone) }
@@ -40,8 +41,7 @@ describe Milestone do
end
it "should count closed issues" do
- IssueObserver.current_user = issue.author
- issue.update_attributes(closed: true)
+ issue.close
milestone.issues << issue
milestone.percent_complete.should == 100
end
@@ -96,7 +96,7 @@ describe Milestone do
describe :items_count do
before do
milestone.issues << create(:issue)
- milestone.issues << create(:issue, closed: true)
+ milestone.issues << create(:closed_issue)
milestone.merge_requests << create(:merge_request)
end
@@ -110,7 +110,35 @@ describe Milestone do
it { milestone.can_be_closed?.should be_true }
end
- describe :open? do
- it { milestone.open?.should be_true }
+ describe :is_empty? do
+ before do
+ issue = create :closed_issue, milestone: milestone
+ merge_request = create :merge_request, milestone: milestone
+ end
+
+ it 'Should return total count of issues and merge requests assigned to milestone' do
+ milestone.total_items_count.should eq 2
+ end
+ end
+
+ describe :can_be_closed? do
+ before do
+ milestone = create :milestone
+ create :closed_issue, milestone: milestone
+
+ issue = create :issue
+ end
+
+ it 'should be true if milestone active and all nested issues closed' do
+ milestone.can_be_closed?.should be_true
+ end
+
+ it 'should be false if milestone active and not all nested issues closed' do
+ issue.milestone = milestone
+ issue.save
+
+ milestone.can_be_closed?.should be_false
+ end
end
+
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index d0de4a7b7fb..0a0509bcd27 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -2,13 +2,14 @@
#
# Table name: namespaces
#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# type :string(255)
+# id :integer not null, primary key
+# name :string(255) not null
+# path :string(255) not null
+# owner_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# type :string(255)
+# description :string(255) default(""), not null
#
require 'spec_helper'
@@ -58,8 +59,8 @@ describe Namespace do
@namespace.stub(path_changed?: true)
end
- it "should raise error when dirtory exists" do
- expect { @namespace.move_dir }.to raise_error("Already exists")
+ it "should raise error when directory exists" do
+ expect { @namespace.move_dir }.to raise_error("namespace directory cannot be moved")
end
it "should move dir if path changed" do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 52e24a78eb4..ef143debcc1 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -13,6 +13,7 @@
# line_code :string(255)
# commit_id :string(255)
# noteable_id :integer
+# st_diff :text
#
require 'spec_helper'
@@ -71,7 +72,6 @@ describe Note do
end
let(:project) { create(:project) }
- let(:commit) { project.repository.commit }
describe "Commit notes" do
let!(:note) { create(:note_on_commit, note: "+1 from me") }
@@ -130,7 +130,7 @@ describe Note do
describe "Merge request notes" do
let!(:note) { create(:note_on_merge_request, note: "+1 from me") }
- it "should not be votable" do
+ it "should be votable" do
note.should be_votable
end
end
@@ -144,12 +144,12 @@ describe Note do
end
describe '#create_status_change_note' do
- let(:project) { create(:project) }
- let(:thing) { create(:issue, project: project) }
- let(:author) { create(:user) }
- let(:status) { 'new_status' }
+ let(:project) { create(:project) }
+ let(:thing) { create(:issue, project: project) }
+ let(:author) { create(:user) }
+ let(:status) { 'new_status' }
- subject { Note.create_status_change_note(thing, author, status) }
+ subject { Note.create_status_change_note(thing, project, author, status, nil) }
it 'creates and saves a Note' do
should be_a Note
@@ -157,9 +157,105 @@ describe Note do
end
its(:noteable) { should == thing }
- its(:project) { should == thing.project }
- its(:author) { should == author }
- its(:note) { should =~ /Status changed to #{status}/ }
+ its(:project) { should == thing.project }
+ its(:author) { should == author }
+ its(:note) { should =~ /Status changed to #{status}/ }
+
+ it 'appends a back-reference if a closing mentionable is supplied' do
+ commit = double('commit', gfm_reference: 'commit 123456')
+ n = Note.create_status_change_note(thing, project, author, status, commit)
+
+ n.note.should =~ /Status changed to #{status} by commit 123456/
+ end
+ end
+
+ describe '#create_cross_reference_note' do
+ let(:project) { create(:project_with_code) }
+ let(:author) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
+ let(:mergereq) { create(:merge_request, target_project: project) }
+ let(:commit) { project.repository.commit }
+
+ # Test all of {issue, merge request, commit} in both the referenced and referencing
+ # roles, to ensure that the correct information can be inferred from any argument.
+
+ context 'issue from a merge request' do
+ subject { Note.create_cross_reference_note(issue, mergereq, author, project) }
+
+ it { should be_valid }
+ its(:noteable) { should == issue }
+ its(:project) { should == issue.project }
+ its(:author) { should == author }
+ its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" }
+ end
+
+ context 'issue from a commit' do
+ subject { Note.create_cross_reference_note(issue, commit, author, project) }
+
+ it { should be_valid }
+ its(:noteable) { should == issue }
+ its(:note) { should == "_mentioned in commit #{commit.sha[0..5]}_" }
+ end
+
+ context 'merge request from an issue' do
+ subject { Note.create_cross_reference_note(mergereq, issue, author, project) }
+
+ it { should be_valid }
+ its(:noteable) { should == mergereq }
+ its(:project) { should == mergereq.project }
+ its(:note) { should == "_mentioned in issue ##{issue.iid}_" }
+ end
+
+ context 'commit from a merge request' do
+ subject { Note.create_cross_reference_note(commit, mergereq, author, project) }
+
+ it { should be_valid }
+ its(:noteable) { should == commit }
+ its(:project) { should == project }
+ its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" }
+ end
+ end
+
+ describe '#cross_reference_exists?' do
+ let(:project) { create :project }
+ let(:author) { create :user }
+ let(:issue) { create :issue }
+ let(:commit0) { double 'commit0', gfm_reference: 'commit 123456' }
+ let(:commit1) { double 'commit1', gfm_reference: 'commit 654321' }
+
+ before do
+ Note.create_cross_reference_note(issue, commit0, author, project)
+ end
+
+ it 'detects if a mentionable has already been mentioned' do
+ Note.cross_reference_exists?(issue, commit0).should be_true
+ end
+
+ it 'detects if a mentionable has not already been mentioned' do
+ Note.cross_reference_exists?(issue, commit1).should be_false
+ end
+ end
+
+ describe '#system?' do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:other) { create(:issue, project: project) }
+ let(:author) { create(:user) }
+
+ it 'should recognize user-supplied notes as non-system' do
+ @note = create(:note_on_issue)
+ @note.should_not be_system
+ end
+
+ it 'should identify status-change notes as system notes' do
+ @note = Note.create_status_change_note(issue, project, author, 'closed', nil)
+ @note.should be_system
+ end
+
+ it 'should identify cross-reference notes as system notes' do
+ @note = Note.create_cross_reference_note(issue, other, author, project)
+ @note.should be_system
+ end
end
describe :authorization do
@@ -207,4 +303,11 @@ describe Note do
it { @abilities.allowed?(@u3, :admin_note, @p1).should be_false }
end
end
+
+ it_behaves_like 'an editable mentionable' do
+ let(:issue) { create :issue, project: project }
+ let(:subject) { create :note, noteable: issue, project: project }
+ let(:backref_text) { issue.gfm_reference }
+ let(:set_mentionable_text) { ->(txt) { subject.note = txt } }
+ end
end
diff --git a/spec/models/project_hooks_spec.rb b/spec/models/project_hooks_spec.rb
deleted file mode 100644
index 65205538213..00000000000
--- a/spec/models/project_hooks_spec.rb
+++ /dev/null
@@ -1,128 +0,0 @@
-require 'spec_helper'
-
-describe Project, "Hooks" do
- let(:project) { create(:project) }
-
- before do
- @key = create(:key, user: project.owner)
- @user = @key.user
- @key_id = @key.identifier
- end
-
- describe "Post Receive Event" do
- it "should create push event" do
- oldrev, newrev, ref = '00000000000000000000000000000000', 'newrev', 'refs/heads/master'
- data = project.post_receive_data(oldrev, newrev, ref, @user)
-
- project.observe_push(data)
- event = Event.last
-
- event.should_not be_nil
- event.project.should == project
- event.action.should == Event::PUSHED
- event.data.should == data
- end
- end
-
- describe "Project hooks" do
- context "with no web hooks" do
- it "raises no errors" do
- lambda {
- project.execute_hooks({})
- }.should_not raise_error
- end
- end
-
- context "with web hooks" do
- before do
- @project_hook = create(:project_hook)
- @project_hook_2 = create(:project_hook)
- project.hooks << [@project_hook, @project_hook_2]
-
- stub_request(:post, @project_hook.url)
- stub_request(:post, @project_hook_2.url)
- end
-
- it "executes multiple web hook" do
- @project_hook.should_receive(:async_execute).once
- @project_hook_2.should_receive(:async_execute).once
-
- project.trigger_post_receive('oldrev', 'newrev', 'refs/heads/master', @user)
- end
- end
-
- context "does not execute web hooks" do
- before do
- @project_hook = create(:project_hook)
- project.hooks << [@project_hook]
- end
-
- it "when pushing a branch for the first time" do
- @project_hook.should_not_receive(:execute)
- project.trigger_post_receive('00000000000000000000000000000000', 'newrev', 'refs/heads/master', @user)
- end
-
- it "when pushing tags" do
- @project_hook.should_not_receive(:execute)
- project.trigger_post_receive('oldrev', 'newrev', 'refs/tags/v1.0.0', @user)
- end
- end
-
- context "when pushing new branches" do
-
- end
-
- context "when gathering commit data" do
- before do
- @oldrev, @newrev, @ref = project.repository.fresh_commits(2).last.sha,
- project.repository.fresh_commits(2).first.sha, 'refs/heads/master'
- @commit = project.repository.fresh_commits(2).first
-
- # Fill nil/empty attributes
- project.description = "This is a description"
-
- @data = project.post_receive_data(@oldrev, @newrev, @ref, @user)
- end
-
- subject { @data }
-
- it { should include(before: @oldrev) }
- it { should include(after: @newrev) }
- it { should include(ref: @ref) }
- it { should include(user_id: project.owner.id) }
- it { should include(user_name: project.owner.name) }
-
- context "with repository data" do
- subject { @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
-
- context "with commits" do
- subject { @data[:commits] }
-
- it { should be_an(Array) }
- it { should have(1).element }
-
- context "the commit" do
- subject { @data[:commits].first }
-
- it { should include(id: @commit.id) }
- it { should include(message: @commit.safe_message) }
- it { should include(timestamp: @commit.date.xmlschema) }
- it { should include(url: "#{Gitlab.config.gitlab.url}/#{project.code}/commit/#{@commit.id}") }
-
- context "with a author" do
- subject { @data[:commits].first[:author] }
-
- it { should include(name: @commit.author_name) }
- it { should include(email: @commit.author_email) }
- end
- end
- end
- end
- end
-end
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
new file mode 100644
index 00000000000..d3a46ebbb84
--- /dev/null
+++ b/spec/models/project_snippet_spec.rb
@@ -0,0 +1,32 @@
+# == Schema Information
+#
+# Table name: snippets
+#
+# id :integer not null, primary key
+# title :string(255)
+# content :text(2147483647)
+# author_id :integer not null
+# project_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# file_name :string(255)
+# expires_at :datetime
+# private :boolean default(TRUE), not null
+# type :string(255)
+#
+
+require 'spec_helper'
+
+describe ProjectSnippet do
+ describe "Associations" do
+ it { should belong_to(:project) }
+ end
+
+ describe "Mass assignment" do
+ it { should_not allow_mass_assignment_of(:project_id) }
+ end
+
+ describe "Validation" do
+ it { should validate_presence_of(:project) }
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 4b620a2fa3e..47ae760a7ed 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -16,11 +16,20 @@
# wiki_enabled :boolean default(TRUE), not null
# namespace_id :integer
# public :boolean default(FALSE), not null
+# issues_tracker :string(255) default("gitlab"), not null
+# issues_tracker_id :string(255)
+# snippets_enabled :boolean default(TRUE), not null
+# last_activity_at :datetime
+# imported :boolean default(FALSE), not null
+# import_url :string(255)
#
require 'spec_helper'
describe Project do
+ before(:each) { enable_observers }
+ after(:each) { disable_observers }
+
describe "Associations" do
it { should belong_to(:group) }
it { should belong_to(:namespace) }
@@ -32,11 +41,12 @@ describe Project do
it { should have_many(:milestones).dependent(:destroy) }
it { should have_many(:users_projects).dependent(:destroy) }
it { should have_many(:notes).dependent(:destroy) }
- it { should have_many(:snippets).dependent(:destroy) }
- it { should have_many(:deploy_keys).dependent(:destroy) }
+ it { should have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) }
+ it { should have_many(:deploy_keys_projects).dependent(:destroy) }
+ it { should have_many(:deploy_keys) }
it { should have_many(:hooks).dependent(:destroy) }
- it { should have_many(:wikis).dependent(:destroy) }
it { should have_many(:protected_branches).dependent(:destroy) }
+ it { should have_one(:forked_project_link).dependent(:destroy) }
end
describe "Mass assignment" do
@@ -48,23 +58,22 @@ describe Project do
let!(:project) { create(:project) }
it { should validate_presence_of(:name) }
- it { should validate_uniqueness_of(:name) }
+ it { should validate_uniqueness_of(:name).scoped_to(:namespace_id) }
it { should ensure_length_of(:name).is_within(0..255) }
it { should validate_presence_of(:path) }
- it { should validate_uniqueness_of(:path) }
+ it { should validate_uniqueness_of(:path).scoped_to(:namespace_id) }
it { should ensure_length_of(:path).is_within(0..255) }
it { should ensure_length_of(:description).is_within(0..2000) }
it { should validate_presence_of(:creator) }
- it { should ensure_inclusion_of(:issues_enabled).in_array([true, false]) }
- it { should ensure_inclusion_of(:wall_enabled).in_array([true, false]) }
- it { should ensure_inclusion_of(:merge_requests_enabled).in_array([true, false]) }
- it { should ensure_inclusion_of(:wiki_enabled).in_array([true, false]) }
+ it { should ensure_length_of(:issues_tracker_id).is_within(0..255) }
+ it { should validate_presence_of(:namespace) }
it "should not allow new projects beyond user limits" do
- project.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 1))
- project.should_not be_valid
- project.errors[:base].first.should match(/Your own projects limit is 1/)
+ project2 = build(:project)
+ project2.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 0))
+ project2.should_not be_valid
+ project2.errors[:limit_reached].first.should match(/Your own projects limit is 0/)
end
end
@@ -72,14 +81,10 @@ describe Project do
it { should respond_to(:url_to_repo) }
it { should respond_to(:repo_exists?) }
it { should respond_to(:satellite) }
- it { should respond_to(:observe_push) }
it { should respond_to(:update_merge_requests) }
it { should respond_to(:execute_hooks) }
- it { should respond_to(:post_receive_data) }
- it { should respond_to(:trigger_post_receive) }
it { should respond_to(:transfer) }
it { should respond_to(:name_with_namespace) }
- it { should respond_to(:namespace_owner) }
it { should respond_to(:owner) }
it { should respond_to(:path_with_namespace) }
end
@@ -95,11 +100,11 @@ describe Project do
end
describe "last_activity methods" do
- let(:project) { create(:project) }
+ let(:project) { create(:project) }
let(:last_event) { double(created_at: Time.now) }
describe "last_activity" do
- it "should alias last_activity to last_event"do
+ it "should alias last_activity to last_event" do
project.stub(last_event: last_event)
project.last_activity.should == last_event
end
@@ -107,8 +112,8 @@ describe Project do
describe 'last_activity_date' do
it 'returns the creation date of the project\'s last event if present' do
- project.stub(last_event: last_event)
- project.last_activity_date.should == last_event.created_at
+ last_activity_event = create(:event, project: project)
+ project.last_activity_at.to_i.should == last_event.created_at.to_i
end
it 'returns the project\'s last update date if it has no events' do
@@ -118,13 +123,10 @@ describe Project do
end
describe :update_merge_requests do
- let(:project) { create(:project) }
+ let(:project) { create(:project_with_code) }
before do
- @merge_request = create(:merge_request,
- project: project,
- merged: false,
- closed: false)
+ @merge_request = create(:merge_request, source_project: project, target_project: project)
@key = create(:key, user_id: project.owner.id)
end
@@ -133,8 +135,7 @@ describe Project do
@merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user)
@merge_request.reload
- @merge_request.merged.should be_true
- @merge_request.closed.should be_true
+ @merge_request.merged?.should be_true
end
it "should update merge request commits with new one if pushed to source branch" do
@@ -156,15 +157,6 @@ describe Project do
it { Project.find_with_namespace('gitlab/gitlab-ci').should == @project }
it { Project.find_with_namespace('gitlab-ci').should be_nil }
end
-
- context 'w/o namespace' do
- before do
- @project = create(:project, name: 'gitlab-ci')
- end
-
- it { Project.find_with_namespace('gitlab-ci').should == @project }
- it { Project.find_with_namespace('gitlab/gitlab-ci').should be_nil }
- end
end
describe :to_param do
@@ -176,14 +168,6 @@ describe Project do
it { @project.to_param.should == "gitlab/gitlab-ci" }
end
-
- context 'w/o namespace' do
- before do
- @project = create(:project, name: 'gitlab-ci')
- end
-
- it { @project.to_param.should == "gitlab-ci" }
- end
end
describe :repository do
@@ -192,9 +176,69 @@ describe Project do
it "should return valid repo" do
project.repository.should be_kind_of(Repository)
end
+ end
+
+ describe :issue_exists? do
+ let(:project) { create(:project) }
+ let(:existed_issue) { create(:issue, project: project) }
+ let(:not_existed_issue) { create(:issue) }
+ let(:ext_project) { create(:redmine_project) }
+
+ it "should be true or if used internal tracker and issue exists" do
+ project.issue_exists?(existed_issue.iid).should be_true
+ end
+
+ it "should be false or if used internal tracker and issue not exists" do
+ project.issue_exists?(not_existed_issue.iid).should be_false
+ end
+
+ it "should always be true if used other tracker" do
+ ext_project.issue_exists?(rand(100)).should be_true
+ end
+ end
+
+ describe :used_default_issues_tracker? do
+ let(:project) { create(:project) }
+ let(:ext_project) { create(:redmine_project) }
+
+ it "should be true if used internal tracker" do
+ project.used_default_issues_tracker?.should be_true
+ end
+
+ it "should be false if used other tracker" do
+ ext_project.used_default_issues_tracker?.should be_false
+ end
+ end
+
+ describe :can_have_issues_tracker_id? do
+ let(:project) { create(:project) }
+ let(:ext_project) { create(:redmine_project) }
+
+ it "should be true for projects with external issues tracker if issues enabled" do
+ ext_project.can_have_issues_tracker_id?.should be_true
+ end
+
+ it "should be false for projects with internal issue tracker if issues enabled" do
+ project.can_have_issues_tracker_id?.should be_false
+ end
+
+ it "should be always false if issues disabled" do
+ project.issues_enabled = false
+ ext_project.issues_enabled = false
- it "should return nil" do
- Project.new(path: "empty").repository.should be_nil
+ project.can_have_issues_tracker_id?.should be_false
+ ext_project.can_have_issues_tracker_id?.should be_false
end
end
+
+ describe :open_branches do
+ let(:project) { create(:project_with_code) }
+
+ before do
+ project.protected_branches.create(name: 'master')
+ end
+
+ it { project.open_branches.map(&:name).should include('bootstrap') }
+ it { project.open_branches.map(&:name).should_not include('master') }
+ end
end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 7803811f395..3e3543e85e1 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -10,9 +10,6 @@ describe ProjectTeam do
it { should respond_to(:masters) }
it { should respond_to(:reporters) }
it { should respond_to(:guests) }
- it { should respond_to(:repository_writers) }
- it { should respond_to(:repository_masters) }
- it { should respond_to(:repository_readers) }
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
deleted file mode 100644
index 71f9b964e70..00000000000
--- a/spec/models/repository_spec.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-require "spec_helper"
-
-describe Repository do
- let(:project) { create(:project) }
- let(:repository) { project.repository }
-
- describe "Respond to" do
- subject { repository }
-
- it { should respond_to(:repo) }
- it { should respond_to(:tree) }
- it { should respond_to(:root_ref) }
- it { should respond_to(:tags) }
- it { should respond_to(:commit) }
- it { should respond_to(:commits) }
- it { should respond_to(:commits_between) }
- it { should respond_to(:commits_with_refs) }
- it { should respond_to(:commits_since) }
- it { should respond_to(:commits_between) }
- end
-
-
- describe "#discover_default_branch" do
- let(:master) { 'master' }
- let(:stable) { 'stable' }
-
- it "returns 'master' when master exists" do
- repository.should_receive(:branch_names).at_least(:once).and_return([stable, master])
- repository.discover_default_branch.should == 'master'
- end
-
- it "returns non-master when master exists but default branch is set to something else" do
- repository.root_ref = 'stable'
- repository.should_receive(:branch_names).at_least(:once).and_return([stable, master])
- repository.discover_default_branch.should == 'stable'
- end
-
- it "returns a non-master branch when only one exists" do
- repository.should_receive(:branch_names).at_least(:once).and_return([stable])
- repository.discover_default_branch.should == 'stable'
- end
-
- it "returns nil when no branch exists" do
- repository.should_receive(:branch_names).at_least(:once).and_return([])
- repository.discover_default_branch.should be_nil
- end
- end
-
- describe :commit do
- it "should return first head commit if without params" do
- repository.commit.id.should == repository.repo.commits.first.id
- end
-
- it "should return valid commit" do
- repository.commit(ValidCommit::ID).should be_valid_commit
- end
-
- it "should return nil" do
- repository.commit("+123_4532530XYZ").should be_nil
- end
- end
-
- describe :tree do
- before do
- @commit = repository.commit(ValidCommit::ID)
- end
-
- it "should raise error w/o arguments" do
- lambda { repository.tree }.should raise_error
- end
-
- it "should return root tree for commit" do
- tree = repository.tree(@commit)
- tree.contents.size.should == ValidCommit::FILES_COUNT
- tree.contents.map(&:name).should == ValidCommit::FILES
- end
-
- it "should return root tree for commit with correct path" do
- tree = repository.tree(@commit, ValidCommit::C_FILE_PATH)
- tree.contents.map(&:name).should == ValidCommit::C_FILES
- end
-
- it "should return root tree for commit with incorrect path" do
- repository.tree(@commit, "invalid_path").should be_nil
- end
- end
-
- describe "fresh commits" do
- it { repository.fresh_commits(3).count.should == 3 }
- it { repository.fresh_commits.first.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" }
- it { repository.fresh_commits.last.id.should == "f403da73f5e62794a0447aca879360494b08f678" }
- end
-
- describe "commits_between" do
- subject do
- commits = repository.commits_between("3a4b4fb4cde7809f033822a171b9feae19d41fff",
- "8470d70da67355c9c009e4401746b1d5410af2e3")
- commits.map { |c| c.id }
- end
-
- it { should have(3).elements }
- it { should include("f0f14c8eaba69ebddd766498a9d0b0e79becd633") }
- it { should_not include("bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a") }
- end
-end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 1a58f680baf..667c80bcf19 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -11,11 +11,14 @@
# updated_at :datetime not null
# active :boolean default(FALSE), not null
# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
#
require 'spec_helper'
describe Service do
+
describe "Associations" do
it { should belong_to :project }
it { should have_one :service_hook }
@@ -24,4 +27,40 @@ describe Service do
describe "Mass assignment" do
it { should_not allow_mass_assignment_of(:project_id) }
end
+
+ describe "Test Button" do
+ before do
+ @service = Service.new
+ end
+
+ describe "Testable" do
+ let (:project) { create :project }
+
+ before do
+ @service.stub(
+ project: project
+ )
+ @testable = @service.can_test?
+ end
+
+ describe :can_test do
+ it { @testable.should == false }
+ end
+ end
+
+ describe "With commits" do
+ let (:project) { create :project_with_code }
+
+ before do
+ @service.stub(
+ project: project
+ )
+ @testable = @service.can_test?
+ end
+
+ describe :can_test do
+ it { @testable.should == true }
+ end
+ end
+ end
end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index e4d1934829f..5fa397207c7 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -4,32 +4,31 @@
#
# id :integer not null, primary key
# title :string(255)
-# content :text
+# content :text(2147483647)
# author_id :integer not null
-# project_id :integer not null
+# project_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# file_name :string(255)
# expires_at :datetime
+# private :boolean default(TRUE), not null
+# type :string(255)
#
require 'spec_helper'
describe Snippet do
describe "Associations" do
- it { should belong_to(:project) }
it { should belong_to(:author).class_name('User') }
it { should have_many(:notes).dependent(:destroy) }
end
describe "Mass assignment" do
it { should_not allow_mass_assignment_of(:author_id) }
- it { should_not allow_mass_assignment_of(:project_id) }
end
describe "Validation" do
it { should validate_presence_of(:author) }
- it { should validate_presence_of(:project) }
it { should validate_presence_of(:title) }
it { should ensure_length_of(:title).is_within(0..255) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 8ab0a0343bb..f6c9f82c4ee 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -22,10 +22,8 @@
# linkedin :string(255) default(""), not null
# twitter :string(255) default(""), not null
# authentication_token :string(255)
-# dark_scheme :boolean default(FALSE), not null
# theme_id :integer default(1), not null
# bio :string(255)
-# blocked :boolean default(FALSE), not null
# failed_attempts :integer default(0)
# locked_at :datetime
# extern_uid :string(255)
@@ -33,6 +31,11 @@
# username :string(255)
# can_create_group :boolean default(TRUE), not null
# can_create_team :boolean default(TRUE), not null
+# state :string(255)
+# color_scheme_id :integer default(1), not null
+# notification_level :integer default(1), not null
+# password_expires_at :datetime
+# created_by_id :integer
#
require 'spec_helper'
@@ -40,6 +43,7 @@ require 'spec_helper'
describe User do
describe "Associations" do
it { should have_one(:namespace) }
+ it { should have_many(:snippets).class_name('Snippet').dependent(:destroy) }
it { should have_many(:users_projects).dependent(:destroy) }
it { should have_many(:groups) }
it { should have_many(:keys).dependent(:destroy) }
@@ -69,28 +73,10 @@ describe User do
describe "Respond to" do
it { should respond_to(:is_admin?) }
- it { should respond_to(:identifier) }
it { should respond_to(:name) }
it { should respond_to(:private_token) }
end
- describe '#identifier' do
- it "should return valid identifier" do
- user = build(:user, email: "test@mail.com")
- user.identifier.should == "test_mail_com"
- end
-
- it "should return identifier without + sign" do
- user = build(:user, email: "test+foo@mail.com")
- user.identifier.should == "test_foo_mail_com"
- end
-
- it "should conform to Gitolite's required identifier pattern" do
- user = build(:user, email: "_test@example.com")
- user.identifier.should == 'test_example_com'
- end
- end
-
describe '#generate_password' do
it "should execute callback when force_random_password specified" do
user = build(:user, force_random_password: true)
@@ -122,26 +108,52 @@ describe User do
ActiveRecord::Base.observers.enable(:user_observer)
@user = create :user
@project = create :project, namespace: @user.namespace
+ @project_2 = create :project, group: create(:group) # Grant MASTER access to the user
+ @project_3 = create :project, group: create(:group) # Grant DEVELOPER access to the user
+
+ @project_2.team << [@user, :master]
+ @project_3.team << [@user, :developer]
end
it { @user.authorized_projects.should include(@project) }
+ it { @user.authorized_projects.should include(@project_2) }
+ it { @user.authorized_projects.should include(@project_3) }
it { @user.owned_projects.should include(@project) }
+ it { @user.owned_projects.should_not include(@project_2) }
+ it { @user.owned_projects.should_not include(@project_3) }
it { @user.personal_projects.should include(@project) }
+ it { @user.personal_projects.should_not include(@project_2) }
+ it { @user.personal_projects.should_not include(@project_3) }
end
describe 'groups' do
before do
ActiveRecord::Base.observers.enable(:user_observer)
@user = create :user
- @group = create :group, owner: @user
+ @group = create :group
+ @group.add_owner(@user)
end
it { @user.several_namespaces?.should be_true }
- it { @user.namespaces.should == [@user.namespace, @group] }
+ it { @user.namespaces.should include(@user.namespace) }
it { @user.authorized_groups.should == [@group] }
it { @user.owned_groups.should == [@group] }
end
+ describe 'group multiple owners' do
+ before do
+ ActiveRecord::Base.observers.enable(:user_observer)
+ @user = create :user
+ @user2 = create :user
+ @group = create :group
+ @group.add_owner(@user)
+
+ @group.add_user(@user2, UsersGroup::OWNER)
+ end
+
+ it { @user2.several_namespaces?.should be_true }
+ end
+
describe 'namespaced' do
before do
ActiveRecord::Base.observers.enable(:user_observer)
@@ -158,7 +170,7 @@ describe User do
it "should block user" do
user.block
- user.blocked.should be_true
+ user.blocked?.should be_true
end
end
@@ -167,13 +179,13 @@ describe User do
User.delete_all
@user = create :user
@admin = create :user, admin: true
- @blocked = create :user, blocked: true
+ @blocked = create :user, state: :blocked
end
it { User.filter("admins").should == [@admin] }
it { User.filter("blocked").should == [@blocked] }
- it { User.filter("wop").should == [@user, @admin, @blocked] }
- it { User.filter(nil).should == [@user, @admin] }
+ it { User.filter("wop").should include(@user, @admin, @blocked) }
+ it { User.filter(nil).should include(@user, @admin) }
end
describe :not_in_project do
@@ -183,16 +195,85 @@ describe User do
@project = create :project
end
- it { User.not_in_project(@project).should == [@user, @project.owner] }
+ it { User.not_in_project(@project).should include(@user, @project.owner) }
end
- describe 'normal user' do
- let(:user) { create(:user, name: 'John Smith') }
+ describe 'user creation' do
+ describe 'normal user' do
+ let(:user) { create(:user, name: 'John Smith') }
- it { user.is_admin?.should be_false }
- it { user.require_ssh_key?.should be_true }
- it { user.can_create_group?.should be_true }
- it { user.can_create_project?.should be_true }
- it { user.first_name.should == 'John' }
+ it { user.is_admin?.should be_false }
+ it { user.require_ssh_key?.should be_true }
+ it { user.can_create_group?.should be_true }
+ it { user.can_create_project?.should be_true }
+ it { user.first_name.should == 'John' }
+ end
+
+ describe 'without defaults' do
+ let(:user) { User.new }
+
+ it "should not apply defaults to user" do
+ user.projects_limit.should == 10
+ user.can_create_group.should be_true
+ user.theme_id.should == Gitlab::Theme::BASIC
+ end
+ end
+ context 'as admin' do
+ describe 'with defaults' do
+ let(:user) { User.build_user({}, as: :admin) }
+
+ it "should apply defaults to user" do
+ user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit
+ user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group
+ user.theme_id.should == Gitlab.config.gitlab.default_theme
+ end
+ end
+
+ describe 'with default overrides' do
+ let(:user) { User.build_user({projects_limit: 123, can_create_group: true, can_create_team: true, theme_id: Gitlab::Theme::BASIC}, as: :admin) }
+
+ it "should apply defaults to user" do
+ Gitlab.config.gitlab.default_projects_limit.should_not == 123
+ Gitlab.config.gitlab.default_can_create_group.should_not be_true
+ Gitlab.config.gitlab.default_theme.should_not == Gitlab::Theme::BASIC
+ user.projects_limit.should == 123
+ user.can_create_group.should be_true
+ user.theme_id.should == Gitlab::Theme::BASIC
+ end
+ end
+ end
+
+ context 'as user' do
+ describe 'with defaults' do
+ let(:user) { User.build_user }
+
+ it "should apply defaults to user" do
+ user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit
+ user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group
+ user.theme_id.should == Gitlab.config.gitlab.default_theme
+ end
+ end
+
+ describe 'with default overrides' do
+ let(:user) { User.build_user(projects_limit: 123, can_create_group: true, theme_id: Gitlab::Theme::BASIC) }
+
+ it "should apply defaults to user" do
+ user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit
+ user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group
+ user.theme_id.should == Gitlab.config.gitlab.default_theme
+ end
+ end
+ end
+ end
+
+ describe 'by_username_or_id' do
+ let(:user1) { create(:user, username: 'foo') }
+
+ it "should get the correct user" do
+ User.by_username_or_id(user1.id).should == user1
+ User.by_username_or_id('foo').should == user1
+ User.by_username_or_id(-1).should be_nil
+ User.by_username_or_id('bar').should be_nil
+ end
end
end
diff --git a/spec/models/user_team_project_relationship_spec.rb b/spec/models/user_team_project_relationship_spec.rb
deleted file mode 100644
index 86150cf305f..00000000000
--- a/spec/models/user_team_project_relationship_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# == Schema Information
-#
-# Table name: user_team_project_relationships
-#
-# id :integer not null, primary key
-# project_id :integer
-# user_team_id :integer
-# greatest_access :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
-require 'spec_helper'
-
-describe UserTeamProjectRelationship do
- pending "add some examples to (or delete) #{__FILE__}"
-end
diff --git a/spec/models/user_team_spec.rb b/spec/models/user_team_spec.rb
deleted file mode 100644
index 76d47f41498..00000000000
--- a/spec/models/user_team_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# == Schema Information
-#
-# Table name: user_teams
-#
-# id :integer not null, primary key
-# name :string(255)
-# path :string(255)
-# owner_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
-require 'spec_helper'
-
-describe UserTeam do
- pending "add some examples to (or delete) #{__FILE__}"
-end
diff --git a/spec/models/user_team_user_relationship_spec.rb b/spec/models/user_team_user_relationship_spec.rb
deleted file mode 100644
index 981ad1e8873..00000000000
--- a/spec/models/user_team_user_relationship_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# == Schema Information
-#
-# Table name: user_team_user_relationships
-#
-# id :integer not null, primary key
-# user_id :integer
-# user_team_id :integer
-# group_admin :boolean
-# permission :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
-require 'spec_helper'
-
-describe UserTeamUserRelationship do
- pending "add some examples to (or delete) #{__FILE__}"
-end
diff --git a/spec/models/users_group_spec.rb b/spec/models/users_group_spec.rb
new file mode 100644
index 00000000000..9264f2bc034
--- /dev/null
+++ b/spec/models/users_group_spec.rb
@@ -0,0 +1,40 @@
+# == Schema Information
+#
+# Table name: users_groups
+#
+# id :integer not null, primary key
+# group_access :integer not null
+# group_id :integer not null
+# user_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# notification_level :integer default(3), not null
+#
+
+require 'spec_helper'
+
+describe UsersGroup do
+ describe "Associations" do
+ it { should belong_to(:group) }
+ it { should belong_to(:user) }
+ end
+
+ describe "Mass assignment" do
+ it { should_not allow_mass_assignment_of(:group_id) }
+ end
+
+ describe "Validation" do
+ let!(:users_group) { create(:users_group) }
+
+ it { should validate_presence_of(:user_id) }
+ it { should validate_uniqueness_of(:user_id).scoped_to(:group_id).with_message(/already exists/) }
+
+ it { should validate_presence_of(:group_id) }
+ it { should ensure_inclusion_of(:group_access).in_array(UsersGroup.group_access_roles.values) }
+ end
+
+ describe "Delegate methods" do
+ it { should respond_to(:user_name) }
+ it { should respond_to(:user_email) }
+ end
+end
diff --git a/spec/models/users_project_spec.rb b/spec/models/users_project_spec.rb
index e8f5b647ce0..e289a592b03 100644
--- a/spec/models/users_project_spec.rb
+++ b/spec/models/users_project_spec.rb
@@ -2,12 +2,13 @@
#
# Table name: users_projects
#
-# id :integer not null, primary key
-# user_id :integer not null
-# project_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# project_access :integer default(0), not null
+# id :integer not null, primary key
+# user_id :integer not null
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# project_access :integer default(0), not null
+# notification_level :integer default(3), not null
#
require 'spec_helper'
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
new file mode 100644
index 00000000000..67f2a6da42d
--- /dev/null
+++ b/spec/models/wiki_page_spec.rb
@@ -0,0 +1,164 @@
+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
+
+ def commit_details
+ commit = {name: user.name, email: user.email, message: "test commit"}
+ end
+
+ def create_page(name, content)
+ wiki.wiki.write_page(name, :markdown, content, commit_details)
+ end
+
+ def destroy_page(title)
+ page = wiki.wiki.paged(title)
+ wiki.wiki.delete_page(page, commit_details)
+ end
+
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:user) { project.owner }
+ let(:wiki) { GollumWiki.new(project, user) }
+
+ subject { WikiPage.new(wiki) }
+
+ before do
+ create_temp_repo(wiki.send(:path_to_repo))
+ end
+
+ describe "#initialize" do
+ context "when initialized with an existing gollum page" do
+ before do
+ create_page("test page", "test content")
+ @page = wiki.wiki.paged("test page")
+ @wiki_page = WikiPage.new(wiki, @page, true)
+ end
+
+ it "sets the slug attribute" do
+ @wiki_page.slug.should == "test-page"
+ end
+
+ it "sets the title attribute" do
+ @wiki_page.title.should == "test page"
+ end
+
+ it "sets the formatted content attribute" do
+ @wiki_page.content.should == "test content"
+ end
+
+ it "sets the format attribute" do
+ @wiki_page.format.should == :markdown
+ end
+
+ it "sets the message attribute" do
+ @wiki_page.message.should == "test commit"
+ end
+
+ it "sets the version attribute" do
+ @wiki_page.version.should be_a Commit
+ end
+ end
+ end
+
+ describe "validations" do
+ before do
+ subject.attributes = {title: 'title', content: 'content'}
+ end
+
+ it "validates presence of title" do
+ subject.attributes.delete(:title)
+ subject.valid?.should be_false
+ end
+
+ it "validates presence of content" do
+ subject.attributes.delete(:content)
+ subject.valid?.should be_false
+ end
+ end
+
+ before do
+ @wiki_attr = {title: "Index", content: "Home Page", format: "markdown"}
+ end
+
+ describe "#create" do
+ after do
+ destroy_page("Index")
+ end
+
+ context "with valid attributes" do
+ it "saves the wiki page" do
+ subject.create(@wiki_attr)
+ wiki.find_page("Index").should_not be_nil
+ end
+
+ it "returns true" do
+ subject.create(@wiki_attr).should == true
+ end
+ end
+ end
+
+ describe "#update" do
+ before do
+ create_page("Update", "content")
+ @page = wiki.find_page("Update")
+ end
+
+ after do
+ destroy_page("Update")
+ end
+
+ context "with valid attributes" do
+ it "updates the content of the page" do
+ @page.update("new content")
+ @page = wiki.find_page("Update")
+ end
+
+ it "returns true" do
+ @page.update("more content").should be_true
+ end
+ end
+ end
+
+ describe "#destroy" do
+ before do
+ create_page("Delete Page", "content")
+ @page = wiki.find_page("Delete Page")
+ end
+
+ it "should delete the page" do
+ @page.delete
+ wiki.pages.should be_empty
+ end
+
+ it "should return true" do
+ @page.delete.should == true
+ end
+ end
+
+ describe "#versions" do
+ before do
+ create_page("Update", "content")
+ @page = wiki.find_page("Update")
+ end
+
+ after do
+ destroy_page("Update")
+ end
+
+ it "returns an array of all commits for the page" do
+ 3.times { |i| @page.update("content #{i}") }
+ @page.versions.count.should == 4
+ end
+ end
+
+end
diff --git a/spec/models/wiki_spec.rb b/spec/models/wiki_spec.rb
deleted file mode 100644
index 9750b81d303..00000000000
--- a/spec/models/wiki_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# == Schema Information
-#
-# Table name: wikis
-#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# project_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-# slug :string(255)
-# user_id :integer
-#
-
-require 'spec_helper'
-
-describe Wiki do
- describe "Associations" do
- it { should belong_to(:project) }
- it { should belong_to(:user) }
- it { should have_many(:notes).dependent(:destroy) }
- end
-
- describe "Mass assignment" do
- it { should_not allow_mass_assignment_of(:project_id) }
- it { should_not allow_mass_assignment_of(:user_id) }
- end
-
- describe "Validation" do
- it { should validate_presence_of(:title) }
- it { should ensure_length_of(:title).is_within(1..250) }
- it { should validate_presence_of(:content) }
- it { should validate_presence_of(:user) }
- end
-end
diff --git a/spec/observers/activity_observer_spec.rb b/spec/observers/activity_observer_spec.rb
index 3d503027360..dc14ab86b6d 100644
--- a/spec/observers/activity_observer_spec.rb
+++ b/spec/observers/activity_observer_spec.rb
@@ -3,24 +3,13 @@ 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 "Merge Request created" do
- before do
- MergeRequest.observers.enable :activity_observer do
- @merge_request = create(:merge_request, project: project)
- @event = Event.last
- end
- end
-
- it_should_be_valid_event
- it { @event.action.should == Event::CREATED }
- it { @event.target.should == @merge_request }
- end
-
describe "Issue created" do
before do
Issue.observers.enable :activity_observer do
@@ -47,4 +36,26 @@ describe ActivityObserver do
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/issue_observer_spec.rb b/spec/observers/issue_observer_spec.rb
index 700c9a3a69b..4155ff31193 100644
--- a/spec/observers/issue_observer_spec.rb
+++ b/spec/observers/issue_observer_spec.rb
@@ -1,198 +1,98 @@
require 'spec_helper'
describe IssueObserver do
- let(:some_user) { double(:user, id: 1) }
- let(:assignee) { double(:user, id: 2) }
- let(:author) { double(:user, id: 3) }
- let(:issue) { double(:issue, id: 42, assignee: assignee, author: author) }
+ let(:some_user) { create :user }
+ let(:assignee) { create :user }
+ let(:author) { create :user }
+ let(:mock_issue) { create(:issue, assignee: assignee, author: author) }
- before(:each) { subject.stub(:current_user).and_return(some_user) }
+
+ before { subject.stub(:current_user).and_return(some_user) }
+ before { subject.stub(:current_commit).and_return(nil) }
+ before { subject.stub(notification: mock('NotificationService').as_null_object) }
+ before { mock_issue.project.stub_chain(:repository, :commit).and_return(nil) }
subject { IssueObserver.instance }
describe '#after_create' do
+ it 'trigger notification to send emails' do
+ subject.should_receive(:notification)
- it 'is called when an issue is created' do
- subject.should_receive(:after_create)
-
- Issue.observers.enable :issue_observer do
- create(:issue, project: create(:project))
- end
+ subject.after_create(mock_issue)
end
- it 'sends an email to the assignee' do
- Notify.should_receive(:new_issue_email).with(issue.id)
+ it 'should create cross-reference notes' do
+ other_issue = create(:issue)
+ mock_issue.stub(references: [other_issue])
- subject.after_create(issue)
- end
-
- it 'does not send an email to the assignee if assignee created the issue' do
- subject.stub(:current_user).and_return(assignee)
- Notify.should_not_receive(:new_issue_email)
-
- subject.after_create(issue)
+ Note.should_receive(:create_cross_reference_note).with(other_issue, mock_issue,
+ some_user, mock_issue.project)
+ subject.after_create(mock_issue)
end
end
- context '#after_update' do
- before(:each) do
- issue.stub(:is_being_reassigned?).and_return(false)
- issue.stub(:is_being_closed?).and_return(false)
- issue.stub(:is_being_reopened?).and_return(false)
- end
-
- it 'is called when an issue is changed' do
- changed = create(:issue, project: create(:project))
- subject.should_receive(:after_update)
-
- Issue.observers.enable :issue_observer do
- changed.description = 'I changed'
- changed.save
- end
- end
-
- context 'a reassigned email' do
- it 'is sent if the issue is being reassigned' do
- issue.should_receive(:is_being_reassigned?).and_return(true)
- subject.should_receive(:send_reassigned_email).with(issue)
-
- subject.after_update(issue)
- end
-
- it 'is not sent if the issue is not being reassigned' do
- issue.should_receive(:is_being_reassigned?).and_return(false)
- subject.should_not_receive(:send_reassigned_email)
-
- subject.after_update(issue)
- end
- end
-
+ context '#after_close' do
context 'a status "closed"' do
+ before { mock_issue.stub(state: 'closed') }
+
it 'note is created if the issue is being closed' do
- issue.should_receive(:is_being_closed?).and_return(true)
- Notify.should_receive(:issue_status_changed_email).twice
- Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
+ Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', nil)
- subject.after_update(issue)
+ subject.after_close(mock_issue, nil)
end
- it 'note is not created if the issue is not being closed' do
- issue.should_receive(:is_being_closed?).and_return(false)
- Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
-
- subject.after_update(issue)
+ it 'trigger notification to send emails' do
+ subject.notification.should_receive(:close_issue).with(mock_issue, some_user)
+ subject.after_close(mock_issue, nil)
end
- it 'notification is delivered if the issue being closed' do
- issue.stub(:is_being_closed?).and_return(true)
- Notify.should_receive(:issue_status_changed_email).twice
- Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
-
- subject.after_update(issue)
- end
+ it 'appends a mention to the closing commit if one is present' do
+ commit = double('commit', gfm_reference: 'commit 123456')
+ subject.stub(current_commit: commit)
- it 'notification is not delivered if the issue not being closed' do
- issue.stub(:is_being_closed?).and_return(false)
- Notify.should_not_receive(:issue_status_changed_email)
- Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
+ Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', commit)
- subject.after_update(issue)
- end
-
- it 'notification is delivered only to author if the issue being closed' do
- issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil)
- issue_without_assignee.stub(:is_being_reassigned?).and_return(false)
- issue_without_assignee.stub(:is_being_closed?).and_return(true)
- issue_without_assignee.stub(:is_being_reopened?).and_return(false)
- Notify.should_receive(:issue_status_changed_email).once
- Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'closed')
-
- subject.after_update(issue_without_assignee)
+ subject.after_close(mock_issue, nil)
end
end
context 'a status "reopened"' do
- it 'note is created if the issue is being reopened' do
- Notify.should_receive(:issue_status_changed_email).twice
- issue.should_receive(:is_being_reopened?).and_return(true)
- Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')
-
- subject.after_update(issue)
- end
-
- it 'note is not created if the issue is not being reopened' do
- issue.should_receive(:is_being_reopened?).and_return(false)
- Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened')
-
- subject.after_update(issue)
- end
-
- it 'notification is delivered if the issue being reopened' do
- issue.stub(:is_being_reopened?).and_return(true)
- Notify.should_receive(:issue_status_changed_email).twice
- Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')
-
- subject.after_update(issue)
- end
+ before { mock_issue.stub(state: 'reopened') }
- it 'notification is not delivered if the issue not being reopened' do
- issue.stub(:is_being_reopened?).and_return(false)
- Notify.should_not_receive(:issue_status_changed_email)
- Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened')
-
- subject.after_update(issue)
- end
-
- it 'notification is delivered only to author if the issue being reopened' do
- issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil)
- issue_without_assignee.stub(:is_being_reassigned?).and_return(false)
- issue_without_assignee.stub(:is_being_closed?).and_return(false)
- issue_without_assignee.stub(:is_being_reopened?).and_return(true)
- Notify.should_receive(:issue_status_changed_email).once
- Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'reopened')
+ it 'note is created if the issue is being reopened' do
+ Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'reopened', nil)
- subject.after_update(issue_without_assignee)
+ subject.after_reopen(mock_issue, nil)
end
end
end
- describe '#send_reassigned_email' do
- let(:previous_assignee) { double(:user, id: 3) }
-
+ context '#after_update' do
before(:each) do
- issue.stub(:assignee_id).and_return(assignee.id)
- issue.stub(:assignee_id_was).and_return(previous_assignee.id)
+ mock_issue.stub(:is_being_reassigned?).and_return(false)
end
- def it_sends_a_reassigned_email_to(recipient)
- Notify.should_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id)
- end
+ context 'notification' do
+ it 'triggered if the issue is being reassigned' do
+ mock_issue.should_receive(:is_being_reassigned?).and_return(true)
+ subject.should_receive(:notification)
- def it_does_not_send_a_reassigned_email_to(recipient)
- Notify.should_not_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id)
- end
+ subject.after_update(mock_issue)
+ end
- it 'sends a reassigned email to the previous and current assignees' do
- it_sends_a_reassigned_email_to assignee.id
- it_sends_a_reassigned_email_to previous_assignee.id
+ it 'is not triggered if the issue is not being reassigned' do
+ mock_issue.should_receive(:is_being_reassigned?).and_return(false)
+ subject.should_not_receive(:notification)
- subject.send(:send_reassigned_email, issue)
+ subject.after_update(mock_issue)
+ end
end
- context 'does not send an email to the user who made the reassignment' do
- it 'if the user is the assignee' do
- subject.stub(:current_user).and_return(assignee)
- it_sends_a_reassigned_email_to previous_assignee.id
- it_does_not_send_a_reassigned_email_to assignee.id
-
- subject.send(:send_reassigned_email, issue)
- end
- it 'if the user is the previous assignee' do
- subject.stub(:current_user).and_return(previous_assignee)
- it_sends_a_reassigned_email_to assignee.id
- it_does_not_send_a_reassigned_email_to previous_assignee.id
+ context 'cross-references' do
+ it 'notices added references' do
+ mock_issue.should_receive(:notice_added_references)
- subject.send(:send_reassigned_email, issue)
+ subject.after_update(mock_issue)
end
end
end
diff --git a/spec/observers/key_observer_spec.rb b/spec/observers/key_observer_spec.rb
index e1412f52e83..b304bf1fcb7 100644
--- a/spec/observers/key_observer_spec.rb
+++ b/spec/observers/key_observer_spec.rb
@@ -2,20 +2,15 @@ require 'spec_helper'
describe KeyObserver do
before do
- @key = double('Key',
- shell_id: 'key-32',
- key: '== a vaild ssh key',
- projects: [],
- is_deploy_key: false
- )
+ @key = create(:personal_key)
@observer = KeyObserver.instance
end
- context :after_save do
+ context :after_create do
it do
GitlabShellWorker.should_receive(:perform_async).with(:add_key, @key.shell_id, @key.key)
- @observer.after_save(@key)
+ @observer.after_create(@key)
end
end
diff --git a/spec/observers/merge_request_observer_spec.rb b/spec/observers/merge_request_observer_spec.rb
index 4841bf88fc5..3f5250a0040 100644
--- a/spec/observers/merge_request_observer_spec.rb
+++ b/spec/observers/merge_request_observer_spec.rb
@@ -1,47 +1,52 @@
require 'spec_helper'
describe MergeRequestObserver do
- let(:some_user) { double(:user, id: 1) }
- let(:assignee) { double(:user, id: 2) }
- let(:author) { double(:user, id: 3) }
- let(:mr) { double(:merge_request, id: 42, assignee: assignee, author: author) }
-
- before(:each) { subject.stub(:current_user).and_return(some_user) }
+ let(:some_user) { create :user }
+ let(:assignee) { create :user }
+ let(:author) { create :user }
+ let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }
+ let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author, target_project: create(:project)) }
+ let(:unassigned_mr) { create(:merge_request, author: author, target_project: create(:project)) }
+ let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author, target_project: create(:project)) }
+ let(:closed_unassigned_mr) { create(:closed_merge_request, author: author, target_project: create(:project)) }
+
+ before { subject.stub(:current_user).and_return(some_user) }
+ before { subject.stub(notification: mock('NotificationService').as_null_object) }
+ before { mr_mock.stub(:author_id) }
+ before { mr_mock.stub(:target_project) }
+ before { mr_mock.stub(:source_project) }
+ before { mr_mock.stub(:project) }
+ before { mr_mock.stub(:create_cross_references!).and_return(true) }
+ before { Repository.any_instance.stub(commit: nil) }
+
+ before(:each) { enable_observers }
+ after(:each) { disable_observers }
subject { MergeRequestObserver.instance }
describe '#after_create' do
-
- it 'is called when a merge request is created' do
- subject.should_receive(:after_create)
-
- MergeRequest.observers.enable :merge_request_observer do
- create(:merge_request, project: create(:project))
- end
+ it 'trigger notification service' do
+ subject.should_receive(:notification)
+ subject.after_create(mr_mock)
end
- it 'sends an email to the assignee' do
- Notify.should_receive(:new_merge_request_email).with(mr.id)
- subject.after_create(mr)
- end
+ it 'creates cross-reference notes' do
+ project = create :project
+ mr_mock.stub(title: "this mr references !#{assigned_mr.id}", project: project)
+ mr_mock.should_receive(:create_cross_references!).with(project, some_user)
- it 'does not send an email to the assignee if assignee created the merge request' do
- subject.stub(:current_user).and_return(assignee)
- Notify.should_not_receive(:new_merge_request_email)
-
- subject.after_create(mr)
+ subject.after_create(mr_mock)
end
end
context '#after_update' do
before(:each) do
- mr.stub(:is_being_reassigned?).and_return(false)
- mr.stub(:is_being_closed?).and_return(false)
- mr.stub(:is_being_reopened?).and_return(false)
+ mr_mock.stub(:is_being_reassigned?).and_return(false)
+ mr_mock.stub(:notice_added_references)
end
it 'is called when a merge request is changed' do
- changed = create(:merge_request, project: create(:project))
+ changed = create(:merge_request, source_project: create(:project))
subject.should_receive(:after_update)
MergeRequest.observers.enable :merge_request_observer do
@@ -50,141 +55,83 @@ describe MergeRequestObserver do
end
end
- context 'a reassigned email' do
+ it 'checks for new references' do
+ mr_mock.should_receive(:notice_added_references)
+
+ subject.after_update(mr_mock)
+ end
+
+ context 'a notification' do
it 'is sent if the merge request is being reassigned' do
- mr.should_receive(:is_being_reassigned?).and_return(true)
- subject.should_receive(:send_reassigned_email).with(mr)
+ mr_mock.should_receive(:is_being_reassigned?).and_return(true)
+ subject.should_receive(:notification)
- subject.after_update(mr)
+ subject.after_update(mr_mock)
end
it 'is not sent if the merge request is not being reassigned' do
- mr.should_receive(:is_being_reassigned?).and_return(false)
- subject.should_not_receive(:send_reassigned_email)
+ mr_mock.should_receive(:is_being_reassigned?).and_return(false)
+ subject.should_not_receive(:notification)
- subject.after_update(mr)
+ subject.after_update(mr_mock)
end
end
+ end
+ context '#after_close' do
context 'a status "closed"' do
it 'note is created if the merge request is being closed' do
- mr.should_receive(:is_being_closed?).and_return(true)
- Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')
+ Note.should_receive(:create_status_change_note).with(assigned_mr, assigned_mr.target_project, some_user, 'closed', nil)
- subject.after_update(mr)
- end
-
- it 'note is not created if the merge request is not being closed' do
- mr.should_receive(:is_being_closed?).and_return(false)
- Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed')
-
- subject.after_update(mr)
- end
-
- it 'notification is delivered if the merge request being closed' do
- mr.stub(:is_being_closed?).and_return(true)
- Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')
-
- subject.after_update(mr)
- end
-
- it 'notification is not delivered if the merge request not being closed' do
- mr.stub(:is_being_closed?).and_return(false)
- Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed')
-
- subject.after_update(mr)
+ assigned_mr.close
end
it 'notification is delivered only to author if the merge request is being closed' do
- mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil)
- mr_without_assignee.stub(:is_being_reassigned?).and_return(false)
- mr_without_assignee.stub(:is_being_closed?).and_return(true)
- mr_without_assignee.stub(:is_being_reopened?).and_return(false)
- Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'closed')
+ Note.should_receive(:create_status_change_note).with(unassigned_mr, unassigned_mr.target_project, some_user, 'closed', nil)
- subject.after_update(mr_without_assignee)
+ unassigned_mr.close
end
end
+ end
+ context '#after_reopen' do
context 'a status "reopened"' do
it 'note is created if the merge request is being reopened' do
- mr.should_receive(:is_being_reopened?).and_return(true)
- Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')
+ Note.should_receive(:create_status_change_note).with(closed_assigned_mr, closed_assigned_mr.target_project, some_user, 'reopened', nil)
- subject.after_update(mr)
- end
-
- it 'note is not created if the merge request is not being reopened' do
- mr.should_receive(:is_being_reopened?).and_return(false)
- Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened')
-
- subject.after_update(mr)
- end
-
- it 'notification is delivered if the merge request being reopened' do
- mr.stub(:is_being_reopened?).and_return(true)
- Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')
-
- subject.after_update(mr)
- end
-
- it 'notification is not delivered if the merge request is not being reopened' do
- mr.stub(:is_being_reopened?).and_return(false)
- Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened')
-
- subject.after_update(mr)
+ closed_assigned_mr.reopen
end
it 'notification is delivered only to author if the merge request is being reopened' do
- mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil)
- mr_without_assignee.stub(:is_being_reassigned?).and_return(false)
- mr_without_assignee.stub(:is_being_closed?).and_return(false)
- mr_without_assignee.stub(:is_being_reopened?).and_return(true)
- Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'reopened')
+ Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, closed_unassigned_mr.target_project, some_user, 'reopened', nil)
- subject.after_update(mr_without_assignee)
+ closed_unassigned_mr.reopen
end
end
end
- describe '#send_reassigned_email' do
- let(:previous_assignee) { double(:user, id: 3) }
-
- before(:each) do
- mr.stub(:assignee_id).and_return(assignee.id)
- mr.stub(:assignee_id_was).and_return(previous_assignee.id)
+ describe "Merge Request created" do
+ def self.it_should_be_valid_event
+ it { @event.should_not be_nil }
+ it { @event.should_not be_nil }
+ it { @event.project.should == project }
+ it { @event.project.should == project }
end
- def it_sends_a_reassigned_email_to(recipient)
- Notify.should_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id)
+ let(:project) { create(:project) }
+ before do
+ TestEnv.enable_observers
+ @merge_request = create(:merge_request, source_project: project, target_project: project)
+ @event = Event.last
end
- def it_does_not_send_a_reassigned_email_to(recipient)
- Notify.should_not_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id)
+ after do
+ TestEnv.disable_observers
end
- it 'sends a reassigned email to the previous and current assignees' do
- it_sends_a_reassigned_email_to assignee.id
- it_sends_a_reassigned_email_to previous_assignee.id
-
- subject.send(:send_reassigned_email, mr)
- end
-
- context 'does not send an email to the user who made the reassignment' do
- it 'if the user is the assignee' do
- subject.stub(:current_user).and_return(assignee)
- it_sends_a_reassigned_email_to previous_assignee.id
- it_does_not_send_a_reassigned_email_to assignee.id
-
- subject.send(:send_reassigned_email, mr)
- end
- it 'if the user is the previous assignee' do
- subject.stub(:current_user).and_return(previous_assignee)
- it_sends_a_reassigned_email_to assignee.id
- it_does_not_send_a_reassigned_email_to previous_assignee.id
-
- subject.send(:send_reassigned_email, mr)
- end
- end
+ it_should_be_valid_event
+ it { @event.action.should == Event::CREATED }
+ it { @event.target.should == @merge_request }
end
+
end
diff --git a/spec/observers/note_observer_spec.rb b/spec/observers/note_observer_spec.rb
index 8ad42c21d2c..f9b96c255c1 100644
--- a/spec/observers/note_observer_spec.rb
+++ b/spec/observers/note_observer_spec.rb
@@ -2,11 +2,12 @@ require 'spec_helper'
describe NoteObserver do
subject { NoteObserver.instance }
+ before { subject.stub(notification: mock('NotificationService').as_null_object) }
let(:team_without_author) { (1..2).map { |n| double :user, id: n } }
+ let(:note) { double(:note).as_null_object }
describe '#after_create' do
- let(:note) { double :note }
it 'is called after a note is created' do
subject.should_receive :after_create
@@ -17,116 +18,39 @@ describe NoteObserver do
end
it 'sends out notifications' do
- subject.should_receive(:send_notify_mails).with(note)
+ subject.should_receive(:notification)
subject.after_create(note)
end
- end
-
- describe "#send_notify_mails" do
- let(:note) { double :note, notify: false, notify_author: false }
-
- it 'notifies team of new note when flagged to notify' do
- note.stub(:notify).and_return(true)
- subject.should_receive(:notify_team).with(note)
-
- subject.after_create(note)
- end
-
- it 'does not notify team of new note when not flagged to notify' do
- subject.should_not_receive(:notify_team).with(note)
-
- subject.after_create(note)
- end
-
- it 'notifies the author of a commit when flagged to notify the author' do
- note.stub(:notify_author).and_return(true)
- note.stub(:noteable).and_return(double(author_email: 'test@test.com'))
- note.stub(:id).and_return(42)
- author = double :user, id: 1, email: 'test@test.com'
- note.stub(:commit_author).and_return(author)
- Notify.should_receive(:note_commit_email)
-
- subject.after_create(note)
- end
-
- it 'does not notify the author of a commit when not flagged to notify the author' do
- notify.should_not_receive(:note_commit_email)
-
- subject.after_create(note)
- end
-
- it 'does nothing if no notify flags are set' do
- subject.after_create(note).should be_nil
- end
- end
- describe '#notify_team' do
- let(:note) { double :note, id: 1 }
+ it 'creates cross-reference notes as appropriate' do
+ @p = create(:project)
+ @referenced = create(:issue, project: @p)
+ @referencer = create(:issue, project: @p)
+ @author = create(:user)
- before :each do
- subject.stub(:team_without_note_author).with(note).and_return(team_without_author)
- end
-
- context 'notifies team of a new note on' do
- it 'a commit' do
- note.stub(:noteable_type).and_return('Commit')
- notify.should_receive(:note_commit_email).twice
-
- subject.send(:notify_team, note)
- end
-
- it 'an issue' do
- note.stub(:noteable_type).and_return('Issue')
- notify.should_receive(:note_issue_email).twice
-
- subject.send(:notify_team, note)
- end
-
- it 'a wiki page' do
- note.stub(:noteable_type).and_return('Wiki')
- notify.should_receive(:note_wiki_email).twice
-
- subject.send(:notify_team, note)
- end
-
- it 'a merge request' do
- note.stub(:noteable_type).and_return('MergeRequest')
- notify.should_receive(:note_merge_request_email).twice
+ Note.should_receive(:create_cross_reference_note).with(@referenced, @referencer, @author, @p)
- subject.send(:notify_team, note)
- end
-
- it 'a wall' do
- # Note: wall posts have #noteable_type of nil
- note.stub(:noteable_type).and_return(nil)
- notify.should_receive(:note_wall_email).twice
-
- subject.send(:notify_team, note)
+ Note.observers.enable :note_observer do
+ create(:note, project: @p, author: @author, noteable: @referencer,
+ note: "Duplicate of ##{@referenced.iid}")
end
end
- it 'does nothing for a new note on a snippet' do
- note.stub(:noteable_type).and_return('Snippet')
+ it "doesn't cross-reference system notes" do
+ Note.should_receive(:create_cross_reference_note).once
- subject.send(:notify_team, note).should be_nil
+ Note.observers.enable :note_observer do
+ Note.create_cross_reference_note(create(:issue), create(:issue))
+ end
end
end
+ describe '#after_update' do
+ it 'checks for new cross-references' do
+ note.should_receive(:notice_added_references)
- describe '#team_without_note_author' do
- let(:author) { double :user, id: 4 }
-
- let(:users) { team_without_author + [author] }
- let(:project) { double :project, users: users }
- let(:note) { double :note, project: project, author: author }
-
- it 'returns the projects user without the note author included' do
- subject.send(:team_without_note_author, note).should == team_without_author
+ subject.after_update(note)
end
end
-
- def notify
- Notify
- end
end
diff --git a/spec/observers/user_observer_spec.rb b/spec/observers/user_observer_spec.rb
index bffa5fcfd69..b74fceb98b1 100644
--- a/spec/observers/user_observer_spec.rb
+++ b/spec/observers/user_observer_spec.rb
@@ -1,7 +1,10 @@
require 'spec_helper'
describe UserObserver do
+ before(:each) { enable_observers }
+ after(:each) {disable_observers}
subject { UserObserver.instance }
+ before { subject.stub(notification: mock('NotificationService').as_null_object) }
it 'calls #after_create when new users are created' do
new_user = build(:user)
@@ -11,11 +14,12 @@ describe UserObserver do
context 'when a new user is created' do
it 'sends an email' do
- Notify.should_receive(:new_user_email)
+ subject.should_receive(:notification)
create(:user)
end
it 'trigger logger' do
+ user = double(:user, id: 42, password: 'P@ssword!', name: 'John', email: 'u@mail.local', extern_uid?: false)
Gitlab::AppLogger.should_receive(:info)
create(:user)
end
diff --git a/spec/observers/users_group_observer_spec.rb b/spec/observers/users_group_observer_spec.rb
new file mode 100644
index 00000000000..3bf562edbb7
--- /dev/null
+++ b/spec/observers/users_group_observer_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe UsersGroupObserver do
+ before(:each) { enable_observers }
+ after(:each) { disable_observers }
+
+ subject { UsersGroupObserver.instance }
+ before { subject.stub(notification: mock('NotificationService').as_null_object) }
+
+ describe "#after_create" do
+ it "should send email to user" do
+ subject.should_receive(:notification)
+ create(:users_group)
+ end
+ end
+
+ describe "#after_update" do
+ before do
+ @membership = create :users_group
+ end
+
+ it "should send email to user" do
+ subject.should_receive(:notification)
+ @membership.update_attribute(:group_access, UsersGroup::MASTER)
+ end
+ end
+end
diff --git a/spec/observers/users_project_observer_spec.rb b/spec/observers/users_project_observer_spec.rb
index 068688b0d1d..e33d8cc50fd 100644
--- a/spec/observers/users_project_observer_spec.rb
+++ b/spec/observers/users_project_observer_spec.rb
@@ -1,9 +1,13 @@
require 'spec_helper'
describe UsersProjectObserver do
+ before(:each) { enable_observers }
+ after(:each) { disable_observers }
+
let(:user) { create(:user) }
let(:project) { create(:project) }
subject { UsersProjectObserver.instance }
+ before { subject.stub(notification: mock('NotificationService').as_null_object) }
describe "#after_commit" do
it "should called when UsersProject created" do
@@ -12,8 +16,8 @@ describe UsersProjectObserver do
end
it "should send email to user" do
- Notify.should_receive(:project_access_granted_email).and_return(double(deliver: true))
- Event.stub(:create => true)
+ subject.should_receive(:notification)
+ Event.stub(create: true)
create(:users_project)
end
@@ -36,7 +40,7 @@ describe UsersProjectObserver do
end
it "should send email to user" do
- Notify.should_receive(:project_access_granted_email)
+ subject.should_receive(:notification)
@users_project.update_attribute(:project_access, UsersProject::MASTER)
end
diff --git a/spec/requests/admin/admin_projects_spec.rb b/spec/requests/admin/admin_projects_spec.rb
deleted file mode 100644
index c9ddf1f4534..00000000000
--- a/spec/requests/admin/admin_projects_spec.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-require 'spec_helper'
-
-describe "Admin::Projects" do
- before do
- @project = create(:project)
- login_as :admin
- end
-
- describe "GET /admin/projects" do
- before do
- visit admin_projects_path
- end
-
- it "should be ok" do
- current_path.should == admin_projects_path
- end
-
- it "should have projects list" do
- page.should have_content(@project.name)
- end
- end
-
- describe "GET /admin/projects/:id" do
- before do
- visit admin_projects_path
- click_link "#{@project.name}"
- end
-
- it "should have project info" do
- page.should have_content(@project.path)
- page.should have_content(@project.name)
- end
- end
-
- describe "GET /admin/projects/:id/edit" do
- before do
- visit admin_projects_path
- click_link "edit_project_#{@project.id}"
- end
-
- it "should have project edit page" do
- page.should have_content("Edit project")
- page.should have_button("Save Project")
- end
-
- describe "Update project" do
- before do
- fill_in "project_name", with: "Big Bang"
- click_button "Save Project"
- @project.reload
- end
-
- it "should show page with new data" do
- page.should have_content("Big Bang")
- end
-
- it "should change project entry" do
- @project.name.should == "Big Bang"
- end
- end
- end
-
- describe "Add new team member" do
- before do
- @new_user = create(:user)
- visit admin_project_path(@project)
- end
-
- it "should create new user" do
- select @new_user.name, from: "user_ids"
- expect { click_button "Add" }.to change { UsersProject.count }.by(1)
- page.should have_content @new_user.name
- current_path.should == admin_project_path(@project)
- end
- end
-end
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb
new file mode 100644
index 00000000000..2fc78a7e390
--- /dev/null
+++ b/spec/requests/api/api_helpers_spec.rb
@@ -0,0 +1,161 @@
+require 'spec_helper'
+
+describe API do
+ include API::APIHelpers
+ include ApiHelpers
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:key) { create(:key, user: user) }
+
+ let(:params) { {} }
+ let(:env) { {} }
+
+ def set_env(token_usr, identifier)
+ clear_env
+ clear_param
+ env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = token_usr.private_token
+ env[API::APIHelpers::SUDO_HEADER] = identifier
+ end
+
+ def set_param(token_usr, identifier)
+ clear_env
+ clear_param
+ params[API::APIHelpers::PRIVATE_TOKEN_PARAM] = token_usr.private_token
+ params[API::APIHelpers::SUDO_PARAM] = identifier
+ end
+
+ def clear_env
+ env.delete(API::APIHelpers::PRIVATE_TOKEN_HEADER)
+ env.delete(API::APIHelpers::SUDO_HEADER)
+ end
+
+ def clear_param
+ params.delete(API::APIHelpers::PRIVATE_TOKEN_PARAM)
+ params.delete(API::APIHelpers::SUDO_PARAM)
+ end
+
+ def error!(message, status)
+ raise Exception
+ end
+
+ describe ".current_user" do
+ it "should leave user as is when sudo not specified" do
+ env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token
+ current_user.should == user
+ clear_env
+ params[API::APIHelpers::PRIVATE_TOKEN_PARAM] = user.private_token
+ current_user.should == user
+ end
+
+ it "should change current user to sudo when admin" do
+ set_env(admin, user.id)
+ current_user.should == user
+ set_param(admin, user.id)
+ current_user.should == user
+ set_env(admin, user.username)
+ current_user.should == user
+ set_param(admin, user.username)
+ current_user.should == user
+ end
+
+ it "should throw an error when the current user is not an admin and attempting to sudo" do
+ set_env(user, admin.id)
+ expect { current_user }.to raise_error
+ set_param(user, admin.id)
+ expect { current_user }.to raise_error
+ set_env(user, admin.username)
+ expect { current_user }.to raise_error
+ set_param(user, admin.username)
+ expect { current_user }.to raise_error
+ end
+
+ it "should throw an error when the user cannot be found for a given id" do
+ id = user.id + admin.id
+ user.id.should_not == id
+ admin.id.should_not == id
+ set_env(admin, id)
+ expect { current_user }.to raise_error
+
+ set_param(admin, id)
+ expect { current_user }.to raise_error
+ end
+
+ it "should throw an error when the user cannot be found for a given username" do
+ username = "#{user.username}#{admin.username}"
+ user.username.should_not == username
+ admin.username.should_not == username
+ set_env(admin, username)
+ expect { current_user }.to raise_error
+
+ set_param(admin, username)
+ expect { current_user }.to raise_error
+ end
+
+ it "should handle sudo's to oneself" do
+ set_env(admin, admin.id)
+ current_user.should == admin
+ set_param(admin, admin.id)
+ current_user.should == admin
+ set_env(admin, admin.username)
+ current_user.should == admin
+ set_param(admin, admin.username)
+ current_user.should == admin
+ end
+
+ it "should handle multiple sudo's to oneself" do
+ set_env(admin, user.id)
+ current_user.should == user
+ current_user.should == user
+ set_env(admin, user.username)
+ current_user.should == user
+ current_user.should == user
+
+ set_param(admin, user.id)
+ current_user.should == user
+ current_user.should == user
+ set_param(admin, user.username)
+ current_user.should == user
+ current_user.should == user
+ end
+
+ it "should handle multiple sudo's to oneself using string ids" do
+ set_env(admin, user.id.to_s)
+ current_user.should == user
+ current_user.should == user
+
+ set_param(admin, user.id.to_s)
+ current_user.should == user
+ current_user.should == user
+ end
+ end
+
+ describe '.sudo_identifier' do
+ it "should return integers when input is an int" do
+ set_env(admin, '123')
+ sudo_identifier.should == 123
+ set_env(admin, '0001234567890')
+ sudo_identifier.should == 1234567890
+
+ set_param(admin, '123')
+ sudo_identifier.should == 123
+ set_param(admin, '0001234567890')
+ sudo_identifier.should == 1234567890
+ end
+
+ it "should return string when input is an is not an int" do
+ set_env(admin, '12.30')
+ sudo_identifier.should == "12.30"
+ set_env(admin, 'hello')
+ sudo_identifier.should == 'hello'
+ set_env(admin, ' 123')
+ sudo_identifier.should == ' 123'
+
+ set_param(admin, '12.30')
+ sudo_identifier.should == "12.30"
+ set_param(admin, 'hello')
+ sudo_identifier.should == 'hello'
+ set_param(admin, ' 123')
+ sudo_identifier.should == ' 123'
+ end
+ end
+end \ No newline at end of file
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index c39a4228408..a6ce72e11e9 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -1,13 +1,18 @@
require 'spec_helper'
-describe Gitlab::API do
+describe API::API do
include ApiHelpers
- let(:user1) { create(:user) }
- let(:user2) { create(:user) }
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
let(:admin) { create(:admin) }
- let!(:group1) { create(:group, owner: user1) }
- let!(:group2) { create(:group, owner: user2) }
+ let!(:group1) { create(:group) }
+ let!(:group2) { create(:group) }
+
+ before do
+ group1.add_owner(user1)
+ group2.add_owner(user2)
+ end
describe "GET /groups" do
context "when unauthenticated" do
@@ -26,7 +31,7 @@ describe Gitlab::API do
json_response.first['name'].should == group1.name
end
end
-
+
context "when authenticated as admin" do
it "admin: should return an array of all groups" do
get api("/groups", admin)
@@ -36,7 +41,7 @@ describe Gitlab::API do
end
end
end
-
+
describe "GET /groups/:id" do
context "when authenticated as user" do
it "should return one of user1's groups" do
@@ -44,32 +49,32 @@ describe Gitlab::API do
response.status.should == 200
json_response['name'] == group1.name
end
-
+
it "should not return a non existing group" do
get api("/groups/1328", user1)
response.status.should == 404
end
-
+
it "should not return a group not attached to user1" do
get api("/groups/#{group2.id}", user1)
- response.status.should == 404
+ response.status.should == 403
end
end
-
+
context "when authenticated as admin" do
it "should return any existing group" do
get api("/groups/#{group2.id}", admin)
response.status.should == 200
json_response['name'] == group2.name
end
-
+
it "should not return a non existing group" do
get api("/groups/1328", admin)
response.status.should == 404
end
end
end
-
+
describe "POST /groups" do
context "when authenticated as user" do
it "should not create group" do
@@ -77,7 +82,7 @@ describe Gitlab::API do
response.status.should == 403
end
end
-
+
context "when authenticated as admin" do
it "should create group" do
post api("/groups", admin), attributes_for(:group)
@@ -85,9 +90,151 @@ describe Gitlab::API do
end
it "should not create group, duplicate" do
- post api("/groups", admin), {:name => "Duplicate Test", :path => group2.path}
+ post api("/groups", admin), {name: "Duplicate Test", path: group2.path}
response.status.should == 404
end
+
+ it "should return 400 bad request error if name not given" do
+ post api("/groups", admin), {path: group2.path}
+ response.status.should == 400
+ end
+
+ it "should return 400 bad request error if path not given" do
+ post api("/groups", admin), { name: 'test' }
+ response.status.should == 400
+ end
+ end
+ end
+
+ describe "POST /groups/:id/projects/:project_id" do
+ let(:project) { create(:project) }
+ before(:each) do
+ project.stub!(:transfer).and_return(true)
+ Project.stub(:find).and_return(project)
+ end
+
+ context "when authenticated as user" do
+ it "should not transfer project to group" do
+ post api("/groups/#{group1.id}/projects/#{project.id}", user2)
+ response.status.should == 403
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "should transfer project to group" do
+ project.should_receive(:transfer)
+ post api("/groups/#{group1.id}/projects/#{project.id}", admin)
+ end
+ end
+ end
+
+ describe "members" do
+ let(:owner) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:master) { create(:user) }
+ let(:guest) { create(:user) }
+ let!(:group_with_members) do
+ group = create(:group)
+ group.add_users([reporter.id], UsersGroup::REPORTER)
+ group.add_users([developer.id], UsersGroup::DEVELOPER)
+ group.add_users([master.id], UsersGroup::MASTER)
+ group.add_users([guest.id], UsersGroup::GUEST)
+ group
+ end
+ let!(:group_no_members) { create(:group) }
+
+ before do
+ group_with_members.add_owner owner
+ group_no_members.add_owner owner
+ end
+
+ describe "GET /groups/:id/members" do
+ context "when authenticated as user that is part or the group" do
+ it "each user: should return an array of members groups of group3" do
+ [owner, master, developer, reporter, guest].each do |user|
+ get api("/groups/#{group_with_members.id}/members", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.size.should == 5
+ json_response.find { |e| e['id']==owner.id }['access_level'].should == UsersGroup::OWNER
+ json_response.find { |e| e['id']==reporter.id }['access_level'].should == UsersGroup::REPORTER
+ json_response.find { |e| e['id']==developer.id }['access_level'].should == UsersGroup::DEVELOPER
+ json_response.find { |e| e['id']==master.id }['access_level'].should == UsersGroup::MASTER
+ json_response.find { |e| e['id']==guest.id }['access_level'].should == UsersGroup::GUEST
+ end
+ end
+
+ it "users not part of the group should get access error" do
+ get api("/groups/#{group_with_members.id}/members", user1)
+ response.status.should == 403
+ end
+ end
+ end
+
+ describe "POST /groups/:id/members" do
+ context "when not a member of the group" do
+ it "should not add guest as member of group_no_members when adding being done by person outside the group" do
+ post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: UsersGroup::MASTER
+ response.status.should == 403
+ end
+ end
+
+ context "when a member of the group" do
+ it "should return ok and add new member" do
+ count_before=group_no_members.users_groups.count
+ new_user = create(:user)
+ post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: UsersGroup::MASTER
+ response.status.should == 201
+ json_response['name'].should == new_user.name
+ json_response['access_level'].should == UsersGroup::MASTER
+ group_no_members.users_groups.count.should == count_before + 1
+ end
+
+ it "should return error if member already exists" do
+ post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: UsersGroup::MASTER
+ response.status.should == 409
+ end
+
+ it "should return a 400 error when user id is not given" do
+ post api("/groups/#{group_no_members.id}/members", owner), access_level: UsersGroup::MASTER
+ response.status.should == 400
+ end
+
+ it "should return a 400 error when access level is not given" do
+ post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
+ response.status.should == 400
+ end
+
+ it "should return a 422 error when access level is not known" do
+ post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
+ response.status.should == 422
+ end
+ end
+ end
+
+ describe "DELETE /groups/:id/members/:user_id" do
+ context "when not a member of the group" do
+ it "should not delete guest's membership of group_with_members" do
+ random_user = create(:user)
+ delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
+ response.status.should == 403
+ end
+ end
+
+ context "when a member of the group" do
+ it "should delete guest's membership of group" do
+ count_before=group_with_members.users_groups.count
+ delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
+ response.status.should == 200
+ group_with_members.users_groups.count.should == count_before - 1
+ end
+
+ it "should return a 404 error when user id is not known" do
+ delete api("/groups/#{group_with_members.id}/members/1328", owner)
+ response.status.should == 404
+ end
+ end
end
end
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
new file mode 100644
index 00000000000..e8870f4d5d8
--- /dev/null
+++ b/spec/requests/api/internal_spec.rb
@@ -0,0 +1,162 @@
+require 'spec_helper'
+
+describe API::API do
+ include ApiHelpers
+ before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
+ after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
+
+ let(:user) { create(:user) }
+ let(:key) { create(:key, user: user) }
+ let(:project) { create(:project) }
+
+ describe "GET /internal/check", no_db: true do
+ it do
+ get api("/internal/check")
+
+ response.status.should == 200
+ json_response['api_version'].should == API::API.version
+ end
+ end
+
+ describe "GET /internal/discover" do
+ it do
+ get(api("/internal/discover"), key_id: key.id)
+
+ response.status.should == 200
+
+ json_response['name'].should == user.name
+ end
+ end
+
+ describe "GET /internal/allowed" do
+ context "access granted" do
+ before do
+ project.team << [user, :developer]
+ end
+
+ context "git pull" do
+ it do
+ pull(key, project)
+
+ response.status.should == 200
+ response.body.should == 'true'
+ end
+ end
+
+ context "git push" do
+ it do
+ push(key, project)
+
+ response.status.should == 200
+ response.body.should == 'true'
+ end
+ end
+ end
+
+ context "access denied" do
+ before do
+ project.team << [user, :guest]
+ end
+
+ context "git pull" do
+ it do
+ pull(key, project)
+
+ response.status.should == 200
+ response.body.should == 'false'
+ end
+ end
+
+ context "git push" do
+ it do
+ push(key, project)
+
+ response.status.should == 200
+ response.body.should == 'false'
+ end
+ end
+ end
+
+ context "blocked user" do
+ let(:personal_project) { create(:project, namespace: user.namespace) }
+
+ before do
+ user.block
+ end
+
+ context "git pull" do
+ it do
+ pull(key, personal_project)
+
+ response.status.should == 200
+ response.body.should == 'false'
+ end
+ end
+
+ context "git push" do
+ it do
+ push(key, personal_project)
+
+ response.status.should == 200
+ response.body.should == 'false'
+ end
+ end
+ end
+
+ context "deploy key" do
+ let(:key) { create(:deploy_key) }
+
+ context "added to project" do
+ before do
+ key.projects << project
+ end
+
+ it do
+ archive(key, project)
+
+ response.status.should == 200
+ response.body.should == 'true'
+ end
+ end
+
+ context "not added to project" do
+ it do
+ archive(key, project)
+
+ response.status.should == 200
+ response.body.should == 'false'
+ end
+ end
+ end
+ end
+
+ def pull(key, project)
+ get(
+ api("/internal/allowed"),
+ ref: 'master',
+ key_id: key.id,
+ project: project.path_with_namespace,
+ action: 'git-upload-pack'
+ )
+ end
+
+ def push(key, project)
+ get(
+ api("/internal/allowed"),
+ ref: 'master',
+ key_id: key.id,
+ project: project.path_with_namespace,
+ action: 'git-receive-pack'
+ )
+ end
+
+ def archive(key, project)
+ get(
+ api("/internal/allowed"),
+ ref: 'master',
+ key_id: key.id,
+ project: project.path_with_namespace,
+ action: 'git-upload-archive'
+ )
+ end
+end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 781ebab026b..a97d6a282a9 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -1,7 +1,9 @@
require 'spec_helper'
-describe Gitlab::API do
+describe API::API do
include ApiHelpers
+ before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
+ after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) }
@@ -41,6 +43,11 @@ describe Gitlab::API do
response.status.should == 200
json_response['title'].should == issue.title
end
+
+ it "should return 404 if issue id not found" do
+ get api("/projects/#{project.id}/issues/54321", user)
+ response.status.should == 404
+ end
end
describe "POST /projects/:id/issues" do
@@ -52,16 +59,37 @@ describe Gitlab::API do
json_response['description'].should be_nil
json_response['labels'].should == ['label', 'label2']
end
+
+ it "should return a 400 bad request if title not given" do
+ post api("/projects/#{project.id}/issues", user), labels: 'label, label2'
+ response.status.should == 400
+ end
end
- describe "PUT /projects/:id/issues/:issue_id" do
+ describe "PUT /projects/:id/issues/:issue_id to update only title" do
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
- title: 'updated title', labels: 'label2', closed: 1
+ title: 'updated title'
response.status.should == 200
+
json_response['title'].should == 'updated title'
+ end
+
+ it "should return 404 error if issue id not found" do
+ put api("/projects/#{project.id}/issues/44444", user),
+ title: 'updated title'
+ response.status.should == 404
+ end
+ end
+
+ describe "PUT /projects/:id/issues/:issue_id to update state and label" do
+ it "should update a project issue" do
+ put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ labels: 'label2', state_event: "close"
+ response.status.should == 200
+
json_response['labels'].should == ['label2']
- json_response['closed'].should be_true
+ json_response['state'].should eq "closed"
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 5da54154a81..2f11f562aa1 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -1,12 +1,15 @@
require "spec_helper"
-describe Gitlab::API do
+describe API::API do
include ApiHelpers
-
- let(:user) { create(:user ) }
- let!(:project) { create(:project, namespace: user.namespace ) }
- let!(:merge_request) { create(:merge_request, author: user, assignee: user, project: project, title: "Test") }
- before { project.team << [user, :reporters] }
+ before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
+ after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
+ let(:user) { create(:user) }
+ let!(:project) {create(:project_with_code, 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") }
+ before {
+ project.team << [user, :reporters]
+ }
describe "GET /projects/:id/merge_requests" do
context "when unauthenticated" do
@@ -32,14 +35,128 @@ describe Gitlab::API do
response.status.should == 200
json_response['title'].should == merge_request.title
end
+
+ it "should return a 404 error if merge_request_id not found" do
+ get api("/projects/#{project.id}/merge_request/999", user)
+ response.status.should == 404
+ end
end
describe "POST /projects/:id/merge_requests" do
+ context 'between branches projects' do
+ it "should return merge_request" do
+ post api("/projects/#{project.id}/merge_requests", user),
+ title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user
+ response.status.should == 201
+ json_response['title'].should == 'Test merge_request'
+ end
+
+ it "should return 422 when source_branch equals target_branch" do
+ post api("/projects/#{project.id}/merge_requests", user),
+ title: "Test merge_request", source_branch: "master", target_branch: "master", author: user
+ response.status.should == 422
+ end
+
+ it "should return 400 when source_branch is missing" do
+ post api("/projects/#{project.id}/merge_requests", user),
+ title: "Test merge_request", target_branch: "master", author: user
+ response.status.should == 400
+ end
+
+ it "should return 400 when target_branch is missing" do
+ post api("/projects/#{project.id}/merge_requests", user),
+ title: "Test merge_request", source_branch: "stable", author: user
+ response.status.should == 400
+ end
+
+ it "should return 400 when title is missing" do
+ post api("/projects/#{project.id}/merge_requests", user),
+ target_branch: 'master', source_branch: 'stable'
+ response.status.should == 400
+ end
+ end
+
+ context 'forked projects' do
+ let!(:user2) {create(:user)}
+ let!(:forked_project_link) { build(:forked_project_link) }
+ let!(:fork_project) { create(:source_project_with_code, forked_project_link: forked_project_link, namespace: user2.namespace, creator_id: user2.id) }
+ let!(:unrelated_project) { create(:target_project_with_code, namespace: user2.namespace, creator_id: user2.id) }
+
+ before :each do |each|
+ fork_project.team << [user2, :reporters]
+ forked_project_link.forked_from_project = project
+ forked_project_link.forked_to_project = fork_project
+ forked_project_link.save!
+ end
+
+ 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
+ response.status.should == 201
+ json_response['title'].should == 'Test merge_request'
+ end
+
+ it "should not return 422 when source_branch equals target_branch" do
+ project.id.should_not == fork_project.id
+ fork_project.forked?.should be_true
+ fork_project.forked_from_project.should == project
+ post api("/projects/#{fork_project.id}/merge_requests", user2),
+ title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
+ response.status.should == 201
+ json_response['title'].should == 'Test merge_request'
+ end
+
+ it "should return 400 when source_branch is missing" do
+ post api("/projects/#{fork_project.id}/merge_requests", user2),
+ title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
+ response.status.should == 400
+ end
+
+ it "should return 400 when target_branch is missing" do
+ post api("/projects/#{fork_project.id}/merge_requests", user2),
+ title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
+ response.status.should == 400
+ end
+
+ it "should return 400 when title is missing" do
+ post api("/projects/#{fork_project.id}/merge_requests", user2),
+ target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: project.id
+ response.status.should == 400
+ end
+
+ it "should return 400 when target_branch is specified and not a forked project" do
+ post api("/projects/#{project.id}/merge_requests", user),
+ title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user, target_project_id: fork_project.id
+ response.status.should == 400
+ end
+
+ it "should return 400 when target_branch is specified and for a different fork" do
+ post api("/projects/#{fork_project.id}/merge_requests", user2),
+ title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: unrelated_project.id
+ response.status.should == 400
+ end
+
+ it "should return 201 when target_branch is specified and for the same project" do
+ post api("/projects/#{fork_project.id}/merge_requests", user2),
+ title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: fork_project.id
+ response.status.should == 201
+ end
+ end
+ end
+
+ describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
it "should return merge_request" do
- post api("/projects/#{project.id}/merge_requests", user),
- title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user
- response.status.should == 201
- json_response['title'].should == 'Test merge_request'
+ put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close"
+ response.status.should == 200
+ json_response['state'].should == 'closed'
+ end
+ end
+
+ describe "PUT /projects/:id/merge_request/:merge_request_id to merge MR" do
+ it "should return merge_request" do
+ put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "merge"
+ response.status.should == 200
+ json_response['state'].should == 'merged'
end
end
@@ -49,6 +166,18 @@ describe Gitlab::API do
response.status.should == 200
json_response['title'].should == 'New title'
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"
+ response.status.should == 422
+ end
+
+ it "should return merge_request with renamed target_branch" do
+ put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki"
+ response.status.should == 200
+ json_response['target_branch'].should == 'wiki'
+ end
end
describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
@@ -57,6 +186,16 @@ describe Gitlab::API do
response.status.should == 201
json_response['note'].should == 'My comment'
end
+
+ it "should return 400 if note is missing" do
+ post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+ response.status.should == 400
+ end
+
+ it "should return 404 if note is attached to non existent merge request" do
+ post api("/projects/#{project.id}/merge_request/111/comments", user), note: "My comment"
+ response.status.should == 404
+ end
end
end
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 80696671462..246fe262ce8 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -1,7 +1,9 @@
require 'spec_helper'
-describe Gitlab::API do
+describe API::API do
include ApiHelpers
+ before(:each) { enable_observers }
+ after(:each) {disable_observers}
let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) }
@@ -16,6 +18,11 @@ describe Gitlab::API do
json_response.should be_an Array
json_response.first['title'].should == milestone.title
end
+
+ it "should return a 401 error if user not authenticated" do
+ get api("/projects/#{project.id}/milestones")
+ response.status.should == 401
+ end
end
describe "GET /projects/:id/milestones/:milestone_id" do
@@ -24,16 +31,38 @@ describe Gitlab::API do
response.status.should == 200
json_response['title'].should == milestone.title
end
+
+ it "should return 401 error if user not authenticated" do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}")
+ response.status.should == 401
+ end
+
+ it "should return a 404 error if milestone id not found" do
+ get api("/projects/#{project.id}/milestones/1234", user)
+ response.status.should == 404
+ end
end
describe "POST /projects/:id/milestones" do
it "should create a new project milestone" do
- post api("/projects/#{project.id}/milestones", user),
- title: 'new milestone'
+ post api("/projects/#{project.id}/milestones", user), title: 'new milestone'
response.status.should == 201
json_response['title'].should == 'new milestone'
json_response['description'].should be_nil
end
+
+ it "should create a new project milestone with description and due date" do
+ post api("/projects/#{project.id}/milestones", user),
+ title: 'new milestone', description: 'release', due_date: '2013-03-02'
+ response.status.should == 201
+ json_response['description'].should == 'release'
+ json_response['due_date'].should == '2013-03-02'
+ end
+
+ it "should return a 400 error if title is missing" do
+ post api("/projects/#{project.id}/milestones", user)
+ response.status.should == 400
+ end
end
describe "PUT /projects/:id/milestones/:milestone_id" do
@@ -43,5 +72,21 @@ describe Gitlab::API do
response.status.should == 200
json_response['title'].should == 'updated title'
end
+
+ it "should return a 404 error if milestone id not found" do
+ put api("/projects/#{project.id}/milestones/1234", user),
+ title: 'updated title'
+ response.status.should == 404
+ end
+ end
+
+ describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do
+ it "should update a project milestone" do
+ put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
+ state_event: 'close'
+ response.status.should == 200
+
+ json_response['state'].should == 'closed'
+ end
end
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index ee99d85df4d..ba18b123039 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -1,13 +1,15 @@
require 'spec_helper'
-describe Gitlab::API do
+describe API::API do
include ApiHelpers
+ before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
+ after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) }
let!(:issue) { create(:issue, project: project, author: user) }
- let!(:merge_request) { create(:merge_request, project: project, author: user) }
- let!(:snippet) { create(:snippet, project: project, author: user) }
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
+ let!(:snippet) { create(:project_snippet, project: project, author: user) }
let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) }
let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) }
@@ -38,6 +40,11 @@ describe Gitlab::API do
response.status.should == 200
json_response['body'].should == wall_note.note
end
+
+ it "should return a 404 error if note not found" do
+ get api("/projects/#{project.id}/notes/123", user)
+ response.status.should == 404
+ end
end
describe "POST /projects/:id/notes" do
@@ -46,6 +53,16 @@ describe Gitlab::API do
response.status.should == 201
json_response['body'].should == 'hi!'
end
+
+ it "should return 401 unauthorized error" do
+ post api("/projects/#{project.id}/notes")
+ response.status.should == 401
+ end
+
+ it "should return a 400 bad request if body is missing" do
+ post api("/projects/#{project.id}/notes", user)
+ response.status.should == 400
+ end
end
describe "GET /projects/:id/noteable/:noteable_id/notes" do
@@ -56,6 +73,11 @@ describe Gitlab::API do
json_response.should be_an Array
json_response.first['body'].should == issue_note.note
end
+
+ it "should return a 404 error when issue id not found" do
+ get api("/projects/#{project.id}/issues/123/notes", user)
+ response.status.should == 404
+ end
end
context "when noteable is a Snippet" do
@@ -65,6 +87,11 @@ describe Gitlab::API do
json_response.should be_an Array
json_response.first['body'].should == snippet_note.note
end
+
+ it "should return a 404 error when snippet id not found" do
+ get api("/projects/#{project.id}/snippets/42/notes", user)
+ response.status.should == 404
+ end
end
context "when noteable is a Merge Request" do
@@ -74,6 +101,11 @@ describe Gitlab::API do
json_response.should be_an Array
json_response.first['body'].should == merge_request_note.note
end
+
+ it "should return a 404 error if merge request id not found" do
+ get api("/projects/#{project.id}/merge_requests/4444/notes", user)
+ response.status.should == 404
+ end
end
end
@@ -84,6 +116,11 @@ describe Gitlab::API do
response.status.should == 200
json_response['body'].should == issue_note.note
end
+
+ it "should return a 404 error if issue note not found" do
+ get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
+ response.status.should == 404
+ end
end
context "when noteable is a Snippet" do
@@ -92,6 +129,11 @@ describe Gitlab::API do
response.status.should == 200
json_response['body'].should == snippet_note.note
end
+
+ it "should return a 404 error if snippet note not found" do
+ get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/123", user)
+ response.status.should == 404
+ end
end
end
@@ -103,6 +145,16 @@ describe Gitlab::API do
json_response['body'].should == 'hi!'
json_response['author']['email'].should == user.email
end
+
+ it "should return a 400 bad request error if body not given" do
+ post api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
+ response.status.should == 400
+ end
+
+ it "should return a 401 unauthorized error if user not authenticated" do
+ post api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!'
+ response.status.should == 401
+ end
end
context "when noteable is a Snippet" do
@@ -112,6 +164,16 @@ describe Gitlab::API do
json_response['body'].should == 'hi!'
json_response['author']['email'].should == user.email
end
+
+ it "should return a 400 bad request error if body not given" do
+ post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
+ response.status.should == 400
+ end
+
+ it "should return a 401 unauthorized error if user not authenticated" do
+ post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!'
+ response.status.should == 401
+ end
end
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 16fd1b9307c..b8c0b6f33ed 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1,16 +1,20 @@
require 'spec_helper'
-describe Gitlab::API do
+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(:admin) { create(:admin) }
+ let!(:project) { create(:project_with_code, creator_id: user.id, namespace: user.namespace) }
let!(:hook) { create(:project_hook, project: project, url: "http://example.com") }
- let!(:project) { create(:project, namespace: user.namespace ) }
- let!(:snippet) { create(:snippet, author: user, project: project, title: 'example') }
+ 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) }
+
before { project.team << [user, :reporter] }
describe "GET /projects" do
@@ -33,6 +37,20 @@ describe Gitlab::API do
end
describe "POST /projects" do
+ context "maximum number of projects reached" do
+ before do
+ (1..user2.projects_limit).each do |project|
+ post api("/projects", user2), name: "foo#{project}"
+ end
+ end
+
+ it "should not create new project" do
+ expect {
+ post api("/projects", user2), name: 'foo'
+ }.to change {Project.count}.by(0)
+ end
+ end
+
it "should create new project without path" do
expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1)
end
@@ -41,14 +59,33 @@ describe Gitlab::API do
expect { post api("/projects", user) }.to_not change {Project.count}
end
+ it "should return a 400 error if name not given" do
+ post api("/projects", user)
+ response.status.should == 400
+ end
+
+ it "should create last project before reaching project limit" do
+ (1..user2.projects_limit-1).each { |p| post api("/projects", user2), name: "foo#{p}" }
+ post api("/projects", user2), name: "foo"
+ response.status.should == 201
+ end
+
it "should respond with 201 on success" do
post api("/projects", user), name: 'foo'
response.status.should == 201
end
- it "should respond with 404 on failure" do
+ it "should respond with 400 if name is not given" do
post api("/projects", user)
- response.status.should == 404
+ response.status.should == 400
+ end
+
+ it "should return a 403 error if project limit reached" do
+ (1..user.projects_limit).each do |p|
+ post api("/projects", user), name: "foo#{p}"
+ end
+ post api("/projects", user), name: 'bar'
+ response.status.should == 403
end
it "should assign attributes to project" do
@@ -68,6 +105,76 @@ describe Gitlab::API do
json_response[k.to_s].should == v
end
end
+
+ it "should set a project as public" do
+ project = attributes_for(:project, { public: true })
+ post api("/projects", user), project
+ json_response['public'].should be_true
+
+ end
+
+ it "should set a project as private" do
+ project = attributes_for(:project, { public: false })
+ post api("/projects", user), project
+ json_response['public'].should be_false
+
+ end
+
+ end
+
+ describe "POST /projects/user/:id" do
+ before { admin }
+
+ it "should create new project without path" do
+ expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1)
+ end
+
+ it "should not create new project without name" do
+ expect { post api("/projects/user/#{user.id}", admin) }.to_not change {Project.count}
+ end
+
+ it "should respond with 201 on success" do
+ post api("/projects/user/#{user.id}", admin), name: 'foo'
+ response.status.should == 201
+ end
+
+ it "should respond with 404 on failure" do
+ post api("/projects/user/#{user.id}", admin)
+ response.status.should == 404
+ end
+
+ it "should assign attributes to project" do
+ project = attributes_for(:project, {
+ description: Faker::Lorem.sentence,
+ default_branch: 'stable',
+ issues_enabled: false,
+ wall_enabled: false,
+ merge_requests_enabled: false,
+ wiki_enabled: false
+ })
+
+ post api("/projects/user/#{user.id}", admin), project
+
+ project.each_pair do |k,v|
+ next if k == :path
+ json_response[k.to_s].should == v
+ end
+ end
+
+ it "should set a project as public" do
+ project = attributes_for(:project, { public: true })
+ post api("/projects/user/#{user.id}", admin), project
+ json_response['public'].should be_true
+
+ end
+
+ it "should set a project as private" do
+ project = attributes_for(:project, { public: false })
+ post api("/projects/user/#{user.id}", admin), project
+ json_response['public'].should be_false
+
+ end
+
end
describe "GET /projects/:id" do
@@ -89,52 +196,34 @@ describe Gitlab::API do
response.status.should == 404
json_response['message'].should == '404 Not Found'
end
- end
- describe "GET /projects/:id/repository/branches" do
- it "should return an array of project branches" do
- get api("/projects/#{project.id}/repository/branches", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name
+ it "should return a 404 error if user is not a member" do
+ other_user = create(:user)
+ get api("/projects/#{project.id}", other_user)
+ response.status.should == 404
end
end
- describe "GET /projects/:id/repository/branches/:branch" do
- it "should return the branch information for a single branch" do
- get api("/projects/#{project.id}/repository/branches/new_design", user)
+ describe "GET /projects/:id/events" do
+ it "should return a project events" do
+ get api("/projects/#{project.id}/events", user)
response.status.should == 200
+ json_event = json_response.first
- json_response['name'].should == 'new_design'
- json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
- json_response['protected'].should == false
+ json_event['action_name'].should == 'joined'
+ json_event['project_id'].to_i.should == project.id
end
- it "should return a 404 error if branch is not available" do
- get api("/projects/#{project.id}/repository/branches/unknown", user)
+ it "should return a 404 error if not found" do
+ get api("/projects/42/events", user)
response.status.should == 404
+ json_response['message'].should == '404 Not Found'
end
- end
-
- describe "PUT /projects/:id/repository/branches/:branch/protect" do
- it "should protect a single branch" do
- put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
- response.status.should == 200
-
- json_response['name'].should == 'new_design'
- json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
- json_response['protected'].should == true
- end
- end
- describe "PUT /projects/:id/repository/branches/:branch/unprotect" do
- it "should unprotect a single branch" do
- put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
- response.status.should == 200
-
- json_response['name'].should == 'new_design'
- json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
- json_response['protected'].should == false
+ it "should return a 404 error if user is not a member" do
+ other_user = create(:user)
+ get api("/projects/#{project.id}/events", other_user)
+ response.status.should == 404
end
end
@@ -144,7 +233,7 @@ describe Gitlab::API do
response.status.should == 200
json_response.should be_an Array
json_response.count.should == 2
- json_response.first['email'].should == user.email
+ json_response.map { |u| u['email'] }.should include user.email
end
it "finds team members with query string" do
@@ -154,6 +243,11 @@ describe Gitlab::API do
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
@@ -163,6 +257,11 @@ describe Gitlab::API do
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
@@ -176,6 +275,34 @@ describe Gitlab::API do
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
@@ -185,6 +312,21 @@ describe Gitlab::API do
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
@@ -193,25 +335,76 @@ describe Gitlab::API do
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
+ end
+
+ describe "DELETE /projects/:id/members/:user_id" do
+ 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/hooks" do
- it "should return project hooks" do
- get api("/projects/#{project.id}/hooks", user)
+ context "authorized user" do
+ it "should return project hooks" do
+ get api("/projects/#{project.id}/hooks", user)
+ response.status.should == 200
- response.status.should == 200
+ json_response.should be_an Array
+ json_response.count.should == 1
+ json_response.first['url'].should == "http://example.com"
+ end
+ end
- json_response.should be_an Array
- json_response.count.should == 1
- json_response.first['url'].should == "http://example.com"
+ context "unauthorized user" do
+ it "should not access project hooks" do
+ get api("/projects/#{project.id}/hooks", user3)
+ response.status.should == 403
+ end
end
end
describe "GET /projects/:id/hooks/:hook_id" do
- it "should return a project hook" do
- get api("/projects/#{project.id}/hooks/#{hook.id}", user)
- response.status.should == 200
- json_response['url'].should == hook.url
+ context "authorized user" do
+ it "should return a project hook" do
+ get api("/projects/#{project.id}/hooks/#{hook.id}", user)
+ response.status.should == 200
+ json_response['url'].should == hook.url
+ end
+
+ it "should return a 404 error if hook id is not available" do
+ get api("/projects/#{project.id}/hooks/1234", user)
+ response.status.should == 404
+ end
+ end
+
+ context "unauthorized user" do
+ it "should not access an existing hook" do
+ get api("/projects/#{project.id}/hooks/#{hook.id}", user3)
+ response.status.should == 403
+ end
+ end
+
+ it "should return a 404 error if hook id is not available" do
+ get api("/projects/#{project.id}/hooks/1234", user)
+ response.status.should == 404
end
end
@@ -219,8 +412,19 @@ describe Gitlab::API do
it "should add hook to project" do
expect {
post api("/projects/#{project.id}/hooks", user),
- "url" => "http://example.com"
+ url: "http://example.com"
}.to change {project.hooks.count}.by(1)
+ response.status.should == 201
+ end
+
+ it "should return a 400 error if url not given" do
+ post api("/projects/#{project.id}/hooks", user)
+ response.status.should == 400
+ end
+
+ it "should return a 422 error if url not valid" do
+ post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com"
+ response.status.should == 422
end
end
@@ -231,45 +435,44 @@ describe Gitlab::API do
response.status.should == 200
json_response['url'].should == 'http://example.org'
end
- end
+ it "should return 404 error if hook id not found" do
+ put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org'
+ response.status.should == 404
+ end
+
+ it "should return 400 error if url is not given" do
+ put api("/projects/#{project.id}/hooks/#{hook.id}", user)
+ response.status.should == 400
+ end
+
+ it "should return a 422 error if url is not valid" do
+ put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com'
+ response.status.should == 422
+ end
+ end
- describe "DELETE /projects/:id/hooks" do
+ describe "DELETE /projects/:id/hooks/:hook_id" do
it "should delete hook from project" do
expect {
- delete api("/projects/#{project.id}/hooks", user),
- hook_id: hook.id
+ delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
}.to change {project.hooks.count}.by(-1)
+ response.status.should == 200
end
- end
- describe "GET /projects/:id/repository/tags" do
- it "should return an array of project tags" do
- get api("/projects/#{project.id}/repository/tags", user)
+ it "should return success when deleting hook" do
+ delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
response.status.should == 200
- json_response.should be_an Array
- json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name
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
+ it "should return success when deleting non existent hook" do
+ delete api("/projects/#{project.id}/hooks/42", user)
+ response.status.should == 200
end
- context "unauthorized user" do
- it "should not return project commits" do
- get api("/projects/#{project.id}/repository/commits")
- response.status.should == 401
- end
+ it "should return a 405 error if hook id not given" do
+ delete api("/projects/#{project.id}/hooks", user)
+ response.status.should == 405
end
end
@@ -288,6 +491,11 @@ describe Gitlab::API do
response.status.should == 200
json_response['title'].should == snippet.title
end
+
+ it "should return a 404 error if snippet id not found" do
+ get api("/projects/#{project.id}/snippets/1234", user)
+ response.status.should == 404
+ end
end
describe "POST /projects/:id/snippets" do
@@ -297,6 +505,24 @@ describe Gitlab::API do
response.status.should == 201
json_response['title'].should == 'api test'
end
+
+ it "should return a 400 error if title is not given" do
+ post api("/projects/#{project.id}/snippets", user),
+ file_name: 'sample.rb', code: 'test'
+ response.status.should == 400
+ end
+
+ it "should return a 400 error if file_name not given" do
+ post api("/projects/#{project.id}/snippets", user),
+ title: 'api test', code: 'test'
+ response.status.should == 400
+ end
+
+ it "should return a 400 error if code not given" do
+ post api("/projects/#{project.id}/snippets", user),
+ title: 'api test', file_name: 'sample.rb'
+ response.status.should == 400
+ end
end
describe "PUT /projects/:id/snippets/:shippet_id" do
@@ -307,6 +533,13 @@ describe Gitlab::API do
json_response['title'].should == 'example'
snippet.reload.content.should == 'updated code'
end
+
+ it "should update an existing project snippet with new title" do
+ put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
+ title: 'other api test'
+ response.status.should == 200
+ json_response['title'].should == 'other api test'
+ end
end
describe "DELETE /projects/:id/snippets/:snippet_id" do
@@ -314,6 +547,12 @@ describe Gitlab::API do
expect {
delete api("/projects/#{project.id}/snippets/#{snippet.id}", user)
}.to change { Snippet.count }.by(-1)
+ response.status.should == 200
+ end
+
+ it "should return success when deleting unknown snippet id" do
+ delete api("/projects/#{project.id}/snippets/1234", user)
+ response.status.should == 200
end
end
@@ -322,22 +561,173 @@ describe Gitlab::API do
get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user)
response.status.should == 200
end
+
+ it "should return a 404 error if raw project snippet not found" do
+ get api("/projects/#{project.id}/snippets/5555/raw", user)
+ response.status.should == 404
+ end
end
- describe "GET /projects/:id/:sha/blob" do
- it "should get the raw file contents" do
- get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user)
- response.status.should == 200
+ describe :deploy_keys do
+ let(:deploy_keys_project) { create(:deploy_keys_project, project: project) }
+ let(:deploy_key) { deploy_keys_project.deploy_key }
+
+ describe "GET /projects/:id/keys" do
+ before { deploy_key }
+
+ it "should return array of ssh keys" do
+ get api("/projects/#{project.id}/keys", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['title'].should == deploy_key.title
+ end
end
- it "should return 404 for invalid branch_name" do
- get api("/projects/#{project.id}/repository/commits/invalid_branch_name/blob?filepath=README.md", user)
- response.status.should == 404
+ describe "GET /projects/:id/keys/:key_id" do
+ it "should return a single key" do
+ get api("/projects/#{project.id}/keys/#{deploy_key.id}", user)
+ response.status.should == 200
+ json_response['title'].should == deploy_key.title
+ end
+
+ it "should return 404 Not Found with invalid ID" do
+ get api("/projects/#{project.id}/keys/404", user)
+ response.status.should == 404
+ end
end
- it "should return 404 for invalid file" do
- get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.invalid", user)
- response.status.should == 404
+ describe "POST /projects/:id/keys" do
+ it "should not create an invalid ssh key" do
+ post api("/projects/#{project.id}/keys", user), { title: "invalid key" }
+ response.status.should == 404
+ end
+
+ it "should create new ssh key" do
+ key_attrs = attributes_for :key
+ expect {
+ post api("/projects/#{project.id}/keys", user), key_attrs
+ }.to change{ project.deploy_keys.count }.by(1)
+ end
+ end
+
+ describe "DELETE /projects/:id/keys/:key_id" do
+ before { deploy_key }
+
+ it "should delete existing key" do
+ expect {
+ delete api("/projects/#{project.id}/keys/#{deploy_key.id}", user)
+ }.to change{ project.deploy_keys.count }.by(-1)
+ end
+
+ it "should return 404 Not Found with invalid ID" do
+ delete api("/projects/#{project.id}/keys/404", user)
+ response.status.should == 404
+ end
+ end
+ end
+
+ describe :fork_admin do
+ let(:project_fork_target) { create(:project) }
+ let(:project_fork_source) { create(:project, public: true) }
+
+ describe "POST /projects/:id/fork/:forked_from_id" do
+ let(:new_project_fork_source) { create(:project, public: true) }
+
+ it "shouldn't available for non admin users" do
+ post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
+ response.status.should == 403
+ end
+
+ it "should allow project to be forked from an existing project" do
+ project_fork_target.forked?.should_not be_true
+ post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
+ response.status.should == 201
+ project_fork_target.reload
+ project_fork_target.forked_from_project.id.should == project_fork_source.id
+ project_fork_target.forked_project_link.should_not be_nil
+ project_fork_target.forked?.should be_true
+ end
+
+ it "should fail if forked_from project which does not exist" do
+ post api("/projects/#{project_fork_target.id}/fork/9999", admin)
+ response.status.should == 404
+ end
+
+ it "should fail with 409 if already forked" do
+ post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
+ project_fork_target.reload
+ project_fork_target.forked_from_project.id.should == project_fork_source.id
+ post api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin)
+ response.status.should == 409
+ project_fork_target.reload
+ project_fork_target.forked_from_project.id.should == project_fork_source.id
+ project_fork_target.forked?.should be_true
+ end
+ end
+
+ describe "DELETE /projects/:id/fork" do
+
+ it "shouldn't available for non admin users" do
+ delete api("/projects/#{project_fork_target.id}/fork", user)
+ response.status.should == 403
+ end
+
+ it "should make forked project unforked" do
+ post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
+ project_fork_target.reload
+ project_fork_target.forked_from_project.should_not be_nil
+ project_fork_target.forked?.should be_true
+ delete api("/projects/#{project_fork_target.id}/fork", admin)
+ response.status.should == 200
+ project_fork_target.reload
+ project_fork_target.forked_from_project.should be_nil
+ project_fork_target.forked?.should_not be_true
+ end
+
+ it "should be idempotent if not forked" do
+ project_fork_target.forked_from_project.should be_nil
+ delete api("/projects/#{project_fork_target.id}/fork", admin)
+ response.status.should == 200
+ project_fork_target.reload.forked_from_project.should be_nil
+ end
+ end
+ end
+
+ describe "GET /projects/search/:query" do
+ let!(:query) { 'query'}
+ let!(:search) { create(:project, name: query, creator_id: user.id, namespace: user.namespace) }
+ let!(:pre) { create(:project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) }
+ let!(:post) { create(:project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) }
+ let!(:pre_post) { create(:project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) }
+ let!(:unfound) { create(:project, name: 'unfound', creator_id: user.id, namespace: user.namespace) }
+ let!(:public) { create(:project, name: "another #{query}",public: true) }
+ let!(:unfound_public) { create(:project, name: 'unfound public', public: true) }
+
+ context "when unauthenticated" do
+ it "should return authentication error" do
+ get api("/projects/search/#{query}")
+ response.status.should == 401
+ end
+ end
+
+ context "when authenticated" do
+ it "should return an array of projects" do
+ get api("/projects/search/#{query}",user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.size.should == 5
+ json_response.each {|project| project['name'].should =~ /.*query.*/}
+ end
+ end
+
+ context "when authenticated as a different user" do
+ it "should return matching public projects" do
+ get api("/projects/search/#{query}", user2)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.size.should == 1
+ json_response.first['name'].should == "another #{query}"
+ end
end
end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
new file mode 100644
index 00000000000..f15abdd3581
--- /dev/null
+++ b/spec/requests/api/repositories_spec.rb
@@ -0,0 +1,217 @@
+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!(:project) { create(:project_with_code, creator_id: user.id) }
+ let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
+
+ before { project.team << [user, :reporter] }
+
+
+ describe "GET /projects/:id/repository/branches" do
+ it "should return an array of project branches" do
+ get api("/projects/#{project.id}/repository/branches", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name
+ end
+ end
+
+ describe "GET /projects/:id/repository/branches/:branch" do
+ it "should return the branch information for a single branch" do
+ get api("/projects/#{project.id}/repository/branches/new_design", user)
+ response.status.should == 200
+
+ json_response['name'].should == 'new_design'
+ json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
+ json_response['protected'].should == false
+ end
+
+ it "should return a 404 error if branch is not available" do
+ get api("/projects/#{project.id}/repository/branches/unknown", user)
+ response.status.should == 404
+ end
+ end
+
+ describe "PUT /projects/:id/repository/branches/:branch/protect" do
+ it "should protect a single branch" do
+ put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
+ response.status.should == 200
+
+ json_response['name'].should == 'new_design'
+ json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
+ json_response['protected'].should == true
+ end
+
+ it "should return a 404 error if branch not found" do
+ put api("/projects/#{project.id}/repository/branches/unknown/protect", user)
+ response.status.should == 404
+ end
+
+ it "should return success when protect branch again" do
+ put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
+ put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
+ response.status.should == 200
+ end
+ end
+
+ describe "PUT /projects/:id/repository/branches/:branch/unprotect" do
+ it "should unprotect a single branch" do
+ put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
+ response.status.should == 200
+
+ json_response['name'].should == 'new_design'
+ json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
+ json_response['protected'].should == false
+ end
+
+ it "should return success when unprotect branch" do
+ put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user)
+ response.status.should == 404
+ end
+
+ it "should return success when unprotect branch again" do
+ put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
+ put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
+ response.status.should == 200
+ end
+ end
+
+ describe "GET /projects/:id/repository/tags" do
+ it "should return an array of project tags" do
+ get api("/projects/#{project.id}/repository/tags", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name
+ 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] }
+
+ it "should return project commits" do
+ get api("/projects/#{project.id}/repository/tree", user)
+ response.status.should == 200
+
+ json_response.should be_an Array
+ json_response.first['name'].should == 'app'
+ json_response.first['type'].should == 'tree'
+ json_response.first['mode'].should == '040000'
+ end
+ end
+
+ context "unauthorized user" do
+ it "should not return project commits" do
+ get api("/projects/#{project.id}/repository/tree")
+ response.status.should == 401
+ end
+ end
+ end
+
+ describe "GET /projects/:id/repository/blobs/:sha" do
+ it "should get the raw file contents" do
+ get api("/projects/#{project.id}/repository/blobs/master?filepath=README.md", user)
+ response.status.should == 200
+ end
+
+ it "should return 404 for invalid branch_name" do
+ get api("/projects/#{project.id}/repository/blobs/invalid_branch_name?filepath=README.md", user)
+ response.status.should == 404
+ end
+
+ it "should return 404 for invalid file" do
+ get api("/projects/#{project.id}/repository/blobs/master?filepath=README.invalid", user)
+ response.status.should == 404
+ end
+
+ it "should return a 400 error if filepath is missing" do
+ get api("/projects/#{project.id}/repository/blobs/master", user)
+ response.status.should == 400
+ end
+ end
+
+ describe "GET /projects/:id/repository/commits/:sha/blob" do
+ it "should get the raw file contents" do
+ get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user)
+ response.status.should == 200
+ end
+ end
+
+end
diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb
index afae8be8cbc..88c17f26a69 100644
--- a/spec/requests/api/session_spec.rb
+++ b/spec/requests/api/session_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::API do
+describe API::API do
include ApiHelpers
let(:user) { create(:user) }
@@ -13,6 +13,10 @@ describe Gitlab::API do
json_response['email'].should == user.email
json_response['private_token'].should == user.private_token
+ json_response['is_admin'].should == user.is_admin?
+ json_response['can_create_team'].should == user.can_create_team?
+ json_response['can_create_project'].should == user.can_create_project?
+ json_response['can_create_group'].should == user.can_create_group?
end
end
@@ -35,5 +39,15 @@ describe Gitlab::API do
json_response['private_token'].should be_nil
end
end
+
+ context "when empty name" do
+ it "should return authentication error" do
+ post api("/session"), password: user.password
+ response.status.should == 401
+
+ json_response['email'].should be_nil
+ json_response['private_token'].should be_nil
+ end
+ end
end
end
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
new file mode 100644
index 00000000000..b1df3cb7886
--- /dev/null
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -0,0 +1,81 @@
+require 'spec_helper'
+
+describe API::API do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:hook) { create(:system_hook, url: "http://example.com") }
+
+ before { stub_request(:post, hook.url) }
+
+ describe "GET /hooks" do
+ context "when no user" do
+ it "should return authentication error" do
+ get api("/hooks")
+ response.status.should == 401
+ end
+ end
+
+ context "when not an admin" do
+ it "should return forbidden error" do
+ get api("/hooks", user)
+ response.status.should == 403
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "should return an array of hooks" do
+ get api("/hooks", admin)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['url'].should == hook.url
+ end
+ end
+ end
+
+ describe "POST /hooks" do
+ it "should create new hook" do
+ expect {
+ post api("/hooks", admin), url: 'http://example.com'
+ }.to change { SystemHook.count }.by(1)
+ end
+
+ it "should respond with 400 if url not given" do
+ post api("/hooks", admin)
+ response.status.should == 400
+ end
+
+ it "should not create new hook without url" do
+ expect {
+ post api("/hooks", admin)
+ }.to_not change { SystemHook.count }
+ end
+ end
+
+ describe "GET /hooks/:id" do
+ it "should return hook by id" do
+ get api("/hooks/#{hook.id}", admin)
+ response.status.should == 200
+ json_response['event_name'].should == 'project_create'
+ end
+
+ it "should return 404 on failure" do
+ get api("/hooks/404", admin)
+ response.status.should == 404
+ end
+ end
+
+ describe "DELETE /hooks/:id" do
+ it "should delete a hook" do
+ expect {
+ delete api("/hooks/#{hook.id}", admin)
+ }.to change { SystemHook.count }.by(-1)
+ end
+
+ it "should return success if hook id not found" do
+ delete api("/hooks/12345", admin)
+ response.status.should == 200
+ end
+ end
+end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 1645117e231..2fced3ec945 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::API do
+describe API::API do
include ApiHelpers
let(:user) { create(:user) }
@@ -31,15 +31,20 @@ describe Gitlab::API do
response.status.should == 200
json_response['email'].should == user.email
end
- end
- describe "POST /users" do
- before{ admin }
+ it "should return a 401 if unauthenticated" do
+ get api("/users/9998")
+ response.status.should == 401
+ end
- it "should not create invalid user" do
- post api("/users", admin), { email: "invalid email" }
+ it "should return a 404 error if user id not found" do
+ get api("/users/9999", user)
response.status.should == 404
end
+ end
+
+ describe "POST /users" do
+ before{ admin }
it "should create user" do
expect {
@@ -47,46 +52,92 @@ describe Gitlab::API do
}.to change { User.count }.by(1)
end
+ it "should return 201 Created on success" do
+ post api("/users", admin), attributes_for(:user, projects_limit: 3)
+ response.status.should == 201
+ end
+
+ it "creating a user should respect default project limit" do
+ limit = 123456
+ Gitlab.config.gitlab.stub(:default_projects_limit).and_return(limit)
+ attr = attributes_for(:user )
+ expect {
+ post api("/users", admin), attr
+ }.to change { User.count }.by(1)
+ user = User.find_by_username(attr[:username])
+ user.projects_limit.should == limit
+ user.theme_id.should == Gitlab::Theme::MARS
+ Gitlab.config.gitlab.unstub(:default_projects_limit)
+ end
+
+ it "should not create user with invalid email" do
+ post api("/users", admin), { email: "invalid email", password: 'password' }
+ response.status.should == 400
+ end
+
+ it "should return 400 error if password not given" do
+ post api("/users", admin), { email: 'test@example.com' }
+ response.status.should == 400
+ end
+
+ it "should return 400 error if email not given" do
+ post api("/users", admin), { password: 'pass1234' }
+ response.status.should == 400
+ end
+
it "shouldn't available for non admin users" do
post api("/users", user), attributes_for(:user)
response.status.should == 403
end
- end
- describe "GET /users/sign_up" do
- before do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(false)
- end
- it "should redirect to sign in page if signup is disabled" do
- get "/users/sign_up"
- response.status.should == 302
- response.should redirect_to(new_user_session_path)
+ context "with existing user" do
+ before { post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test' } }
+
+ it "should not create user with same email" do
+ expect {
+ post api("/users", admin), { email: 'test@example.com', password: 'password' }
+ }.to change { User.count }.by(0)
+ end
+
+ it "should return 409 conflict error if user with email exists" do
+ post api("/users", admin), { email: 'test@example.com', password: 'password' }
+ end
+
+ it "should return 409 conflict error if same username exists" do
+ post api("/users", admin), { email: 'foo@example.com', password: 'pass', username: 'test' }
+ end
end
end
describe "GET /users/sign_up" do
- before do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
- end
- it "should return sign up page if signup is enabled" do
- get "/users/sign_up"
- response.status.should == 200
+ context 'enabled' do
+ before do
+ Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
+ end
+
+ it "should return sign up page if signup is enabled" do
+ get "/users/sign_up"
+ response.status.should == 200
+ end
end
- it "should create a new user account" do
- visit new_user_registration_path
- fill_in "user_name", with: "Name Surname"
- fill_in "user_username", with: "Great"
- fill_in "user_email", with: "name@mail.com"
- fill_in "user_password", with: "password1234"
- fill_in "user_password_confirmation", with: "password1234"
- expect { click_button "Sign up" }.to change {User.count}.by(1)
+
+ context 'disabled' do
+ before do
+ Gitlab.config.gitlab.stub(:signup_enabled).and_return(false)
+ end
+
+ it "should redirect to sign in page if signup is disabled" do
+ get "/users/sign_up"
+ response.status.should == 302
+ response.should redirect_to(new_user_session_path)
+ end
end
end
describe "PUT /users/:id" do
before { admin }
- it "should update user" do
+ it "should update user with new bio" do
put api("/users/#{user.id}", admin), {bio: 'new test bio'}
response.status.should == 200
json_response['bio'].should == 'new test bio'
@@ -108,6 +159,41 @@ describe Gitlab::API do
put api("/users/999999", admin), {bio: 'update should fail'}
response.status.should == 404
end
+
+ context "with existing user" do
+ before {
+ post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test', name: 'test' }
+ post api("/users", admin), { email: 'foo@bar.com', password: 'password', username: 'john', name: 'john' }
+ @user_id = User.all.last.id
+ }
+
+# it "should return 409 conflict error if email address exists" do
+# put api("/users/#{@user_id}", admin), { email: 'test@example.com' }
+# response.status.should == 409
+# end
+#
+# it "should return 409 conflict error if username taken" do
+# @user_id = User.all.last.id
+# put api("/users/#{@user_id}", admin), { username: 'test' }
+# response.status.should == 409
+# end
+ end
+ end
+
+ describe "POST /users/:id/keys" do
+ before { admin }
+
+ it "should not create invalid ssh key" do
+ post api("/users/#{user.id}/keys", admin), { title: "invalid key" }
+ response.status.should == 404
+ end
+
+ it "should create ssh key" do
+ key_attrs = attributes_for :key
+ expect {
+ post api("/users/#{user.id}/keys", admin), key_attrs
+ }.to change{ user.keys.count }.by(1)
+ end
end
describe "DELETE /users/:id" do
@@ -120,6 +206,11 @@ describe Gitlab::API do
json_response['email'].should == user.email
end
+ it "should not delete for unauthenticated user" do
+ delete api("/users/#{user.id}")
+ response.status.should == 401
+ end
+
it "shouldn't available for non admin users" do
delete api("/users/#{user.id}", user)
response.status.should == 403
@@ -136,6 +227,15 @@ describe Gitlab::API do
get api("/user", user)
response.status.should == 200
json_response['email'].should == user.email
+ json_response['is_admin'].should == user.is_admin?
+ json_response['can_create_team'].should == user.can_create_team?
+ json_response['can_create_project'].should == user.can_create_project?
+ json_response['can_create_group'].should == user.can_create_group?
+ end
+
+ it "should return 401 error if user is unauthenticated" do
+ get api("/user")
+ response.status.should == 401
end
end
@@ -160,7 +260,7 @@ describe Gitlab::API do
end
describe "GET /user/keys/:id" do
- it "should returm single key" do
+ it "should return single key" do
user.keys << key
user.save
get api("/user/keys/#{key.id}", user)
@@ -172,19 +272,38 @@ describe Gitlab::API do
get api("/user/keys/42", user)
response.status.should == 404
end
- end
- describe "POST /user/keys" do
- it "should not create invalid ssh key" do
- post api("/user/keys", user), { title: "invalid key" }
+ it "should return 404 error if admin accesses user's ssh key" do
+ user.keys << key
+ user.save
+ admin
+ get api("/user/keys/#{key.id}", admin)
response.status.should == 404
end
+ end
+ describe "POST /user/keys" do
it "should create ssh key" do
key_attrs = attributes_for :key
expect {
post api("/user/keys", user), key_attrs
}.to change{ user.keys.count }.by(1)
+ response.status.should == 201
+ end
+
+ it "should return a 401 error if unauthorized" do
+ post api("/user/keys"), title: 'some title', key: 'some key'
+ response.status.should == 401
+ end
+
+ it "should not create ssh key without key" do
+ post api("/user/keys", user), title: 'title'
+ response.status.should == 400
+ end
+
+ it "should not create ssh key without title" do
+ post api("/user/keys", user), key: "somekey"
+ response.status.should == 400
end
end
@@ -195,11 +314,19 @@ describe Gitlab::API do
expect {
delete api("/user/keys/#{key.id}", user)
}.to change{user.keys.count}.by(-1)
+ response.status.should == 200
end
- it "should return 404 Not Found within invalid ID" do
+ it "should return success if key ID not found" do
delete api("/user/keys/42", user)
- response.status.should == 404
+ response.status.should == 200
+ end
+
+ it "should return 401 error if unauthorized" do
+ user.keys << key
+ user.save
+ delete api("/user/keys/#{key.id}")
+ response.status.should == 401
end
end
end
diff --git a/spec/requests/gitlab_flavored_markdown_spec.rb b/spec/requests/gitlab_flavored_markdown_spec.rb
deleted file mode 100644
index 9a568511fa0..00000000000
--- a/spec/requests/gitlab_flavored_markdown_spec.rb
+++ /dev/null
@@ -1,223 +0,0 @@
-require 'spec_helper'
-
-describe "Gitlab Flavored Markdown" do
- let(:project) { create(:project) }
- let(:issue) { create(:issue, project: project) }
- let(:merge_request) { create(:merge_request, project: project) }
- let(:fred) do
- u = create(:user, name: "fred")
- project.team << [u, :master]
- u
- end
-
- before do
- # add test branch
- @branch_name = "gfm-test"
- r = project.repo
- i = r.index
- # add test file
- @test_file = "gfm_test_file"
- i.add(@test_file, "foo\nbar\n")
- # add commit with gfm
- i.commit("fix ##{issue.id}\n\nask @#{fred.username} for details", head: @branch_name)
-
- # add test tag
- @tag_name = "gfm-test-tag"
- r.git.native(:tag, {}, @tag_name, commit.id)
- end
-
- after do
- # delete test branch and tag
- project.repo.git.native(:branch, {D: true}, @branch_name)
- project.repo.git.native(:tag, {d: true}, @tag_name)
- project.repo.gc_auto
- end
-
- let(:commit) { project.repository.commits(@branch_name).first }
-
- before do
- login_as :user
- project.team << [@user, :developer]
- end
-
- describe "for commits" do
- it "should render title in commits#index" do
- visit project_commits_path(project, @branch_name, limit: 1)
-
- page.should have_link("##{issue.id}")
- end
-
- it "should render title in commits#show" do
- visit project_commit_path(project, commit)
-
- page.should have_link("##{issue.id}")
- end
-
- it "should render description in commits#show" do
- visit project_commit_path(project, commit)
-
- page.should have_link("@#{fred.username}")
- end
-
- it "should render title in refs#tree", js: true do
- visit project_tree_path(project, @branch_name)
-
- within(".tree_commit") do
- page.should have_link("##{issue.id}")
- end
- end
-
- # @wip
- #it "should render title in refs#blame" do
- #visit project_blame_path(project, File.join(@branch_name, @test_file))
-
- #within(".blame_commit") do
- #page.should have_link("##{issue.id}")
- #end
- #end
-
- it "should render title in repositories#branches" do
- visit branches_project_repository_path(project)
-
- page.should have_link("##{issue.id}")
- end
- end
-
- describe "for issues" do
- before do
- @other_issue = create(:issue,
- author: @user,
- assignee: @user,
- project: project)
- @issue = create(:issue,
- author: @user,
- assignee: @user,
- project: project,
- title: "fix ##{@other_issue.id}",
- description: "ask @#{fred.username} for details")
- end
-
- it "should render subject in issues#index" do
- visit project_issues_path(project)
-
- page.should have_link("##{@other_issue.id}")
- end
-
- it "should render subject in issues#show" do
- visit project_issue_path(project, @issue)
-
- page.should have_link("##{@other_issue.id}")
- end
-
- it "should render details in issues#show" do
- visit project_issue_path(project, @issue)
-
- page.should have_link("@#{fred.username}")
- end
- end
-
-
- describe "for merge requests" do
- before do
- @merge_request = create(:merge_request,
- project: project,
- title: "fix ##{issue.id}")
- end
-
- it "should render title in merge_requests#index" do
- visit project_merge_requests_path(project)
-
- page.should have_link("##{issue.id}")
- end
-
- it "should render title in merge_requests#show" do
- visit project_merge_request_path(project, @merge_request)
-
- page.should have_link("##{issue.id}")
- end
- end
-
-
- describe "for milestones" do
- before do
- @milestone = create(:milestone,
- project: project,
- title: "fix ##{issue.id}",
- description: "ask @#{fred.username} for details")
- end
-
- it "should render title in milestones#index" do
- visit project_milestones_path(project)
-
- page.should have_link("##{issue.id}")
- end
-
- it "should render title in milestones#show" do
- visit project_milestone_path(project, @milestone)
-
- page.should have_link("##{issue.id}")
- end
-
- it "should render description in milestones#show" do
- visit project_milestone_path(project, @milestone)
-
- page.should have_link("@#{fred.username}")
- end
- end
-
-
- describe "for notes" do
- it "should render in commits#show", js: true do
- visit project_commit_path(project, commit)
- fill_in "note_note", with: "see ##{issue.id}"
- click_button "Add Comment"
-
- page.should have_link("##{issue.id}")
- end
-
- it "should render in issue#show", js: true do
- visit project_issue_path(project, issue)
- fill_in "note_note", with: "see ##{issue.id}"
- click_button "Add Comment"
-
- page.should have_link("##{issue.id}")
- end
-
- it "should render in merge_request#show", js: true do
- visit project_merge_request_path(project, merge_request)
- fill_in "note_note", with: "see ##{issue.id}"
- click_button "Add Comment"
-
- page.should have_link("##{issue.id}")
- end
-
- it "should render in projects#wall", js: true do
- visit wall_project_path(project)
- fill_in "note_note", with: "see ##{issue.id}"
- click_button "Add Comment"
-
- page.should have_link("##{issue.id}")
- end
- end
-
-
- describe "for wikis" do
- before do
- visit project_wiki_path(project, :index)
- fill_in "Title", with: "Circumvent ##{issue.id}"
- fill_in "Content", with: "# Other pages\n\n* [Foo](foo)\n* [Bar](bar)\n\nAlso look at ##{issue.id} :-)"
- click_on "Save"
- end
-
- it "should NOT render title in wikis#show" do
- within(".content h3") do # page title
- page.should have_content("Circumvent ##{issue.id}")
- page.should_not have_link("##{issue.id}")
- end
- end
-
- it "should render content in wikis#show" do
- page.should have_link("##{issue.id}")
- end
- end
-end
diff --git a/spec/requests/notes_on_merge_requests_spec.rb b/spec/requests/notes_on_merge_requests_spec.rb
deleted file mode 100644
index 0111cf42ac7..00000000000
--- a/spec/requests/notes_on_merge_requests_spec.rb
+++ /dev/null
@@ -1,232 +0,0 @@
-require 'spec_helper'
-
-describe "On a merge request", js: true do
- let!(:project) { create(:project) }
- let!(:merge_request) { create(:merge_request, project: project) }
-
- before do
- login_as :user
- project.team << [@user, :master]
-
- visit project_merge_request_path(project, merge_request)
- end
-
- subject { page }
-
- describe "the note form" do
- # main target form creation
- it { should have_css(".js-main-target-form", visible: true, count: 1) }
-
- # button initalization
- it { within(".js-main-target-form") { should have_button("Add Comment") } }
- it { within(".js-main-target-form") { should_not have_link("Cancel") } }
-
- # notifiactions
- it { within(".js-main-target-form") { should have_checked_field("Notify team via email") } }
- it { within(".js-main-target-form") { should_not have_checked_field("Notify commit author") } }
- it { within(".js-main-target-form") { should_not have_unchecked_field("Notify commit author") } }
-
- describe "without text" do
- it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) } }
- end
-
- describe "with text" do
- before do
- within(".js-main-target-form") do
- fill_in "note[note]", with: "This is awesome"
- end
- end
-
- it { within(".js-main-target-form") { should_not have_css(".js-comment-button[disabled]") } }
-
- it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: true) } }
- end
-
- describe "with preview" do
- before do
- within(".js-main-target-form") do
- fill_in "note[note]", with: "This is awesome"
- find(".js-note-preview-button").trigger("click")
- end
- end
-
- it { within(".js-main-target-form") { should have_css(".js-note-preview", text: "This is awesome", visible: true) } }
-
- it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) } }
- it { within(".js-main-target-form") { should have_css(".js-note-edit-button", visible: true) } }
- end
- end
-
- describe "when posting a note" do
- before do
- within(".js-main-target-form") do
- fill_in "note[note]", with: "This is awsome!"
- find(".js-note-preview-button").trigger("click")
- click_button "Add Comment"
- end
- end
-
- # note added
- it { within(".js-main-target-form") { should have_content("This is awsome!") } }
-
- # reset form
- it { within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") } }
-
- # return from preview
- it { within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) } }
- it { within(".js-main-target-form") { should have_css(".js-note-text", visible: true) } }
-
-
- it "should be removable" do
- find(".js-note-delete").trigger("click")
-
- should_not have_css(".note")
- end
- 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, project: project) }
-
- before do
- login_as :user
- project.team << [@user, :master]
-
- visit diffs_project_merge_request_path(project, merge_request)
-
- click_link("Diff")
- end
-
- subject { page }
-
- describe "when adding a note" do
- before do
- find("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder .js-add-diff-note-button").trigger("click")
- end
-
- describe "the notes holder" do
- it { should have_css("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder + .js-temp-notes-holder") }
-
- it { within(".js-temp-notes-holder") { should have_css(".new_note") } }
- end
-
- describe "the note form" do
- # set up hidden fields correctly
- it { within(".js-temp-notes-holder") { find("#note_noteable_type").value.should == "MergeRequest" } }
- it { within(".js-temp-notes-holder") { find("#note_noteable_id").value.should == merge_request.id.to_s } }
- it { within(".js-temp-notes-holder") { find("#note_commit_id").value.should == "" } }
- it { within(".js-temp-notes-holder") { find("#note_line_code").value.should == "4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185" } }
-
- # buttons
- it { should have_button("Add Comment") }
- it { should have_css(".js-close-discussion-note-form", text: "Cancel") }
-
- # notification options
- it { should have_checked_field("Notify team via email") }
-
- it "shouldn't add a second form for same row" do
- find("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder .js-add-diff-note-button").trigger("click")
-
- should have_css("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder + .js-temp-notes-holder form", count: 1)
- end
-
- it "should be removed when canceled" do
- find(".js-close-discussion-note-form").trigger("click")
-
- should have_no_css(".js-temp-notes-holder")
- end
- end
- end
-
- describe "with muliple note forms" do
- before do
- find("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder .js-add-diff-note-button").trigger("click")
- find("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder .js-add-diff-note-button").trigger("click")
- end
-
- # has two line forms
- it { should have_css(".js-temp-notes-holder", count: 2) }
-
- describe "previewing them separately" do
- before do
- # add two separate texts and trigger previews on both
- within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder + .js-temp-notes-holder") do
- fill_in "note[note]", with: "One comment on line 185"
- find(".js-note-preview-button").trigger("click")
- end
- within("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .js-temp-notes-holder") do
- fill_in "note[note]", with: "Another comment on line 17"
- find(".js-note-preview-button").trigger("click")
- end
- end
-
- # check if previews were rendered separately
- it { within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185.line_holder + .js-temp-notes-holder") { should have_css(".js-note-preview", text: "One comment on line 185") } }
- it { within("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .js-temp-notes-holder") { should have_css(".js-note-preview", text: "Another comment on line 17") } }
- end
-
- describe "posting a note" do
- before do
- within("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .js-temp-notes-holder") do
- fill_in "note[note]", with: "Another comment on line 17"
- click_button("Add Comment")
- end
- end
-
- # removed form after submit
- it { should have_no_css("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .js-temp-notes-holder") }
-
- # added discussion
- it { should have_content("Another comment on line 17") }
- it { should have_css("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .notes_holder") }
- it { should have_css("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .notes_holder .note", count: 1) }
- it { should have_link("Reply") }
-
- it "should remove last note of a discussion" do
- within("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + .notes_holder") do
- find(".js-note-delete").trigger("click")
- end
-
- # removed whole discussion
- should_not have_css(".note_holder")
- should have_css("#342e16cbbd482ac2047dc679b2749d248cc1428f_18_17.line_holder + #342e16cbbd482ac2047dc679b2749d248cc1428f_18_18.line_holder")
- end
- end
- end
-
- describe "when replying to a note" do
- before do
- # create first note
- find("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder .js-add-diff-note-button").trigger("click")
- within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder + .js-temp-notes-holder") do
- fill_in "note[note]", with: "One comment on line 184"
- click_button("Add Comment")
- end
- # create second note
- within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder + .notes_holder") do
- find(".js-discussion-reply-button").trigger("click")
- fill_in "note[note]", with: "An additional comment in reply"
- click_button("Add Comment")
- end
- end
-
- # inserted note
- it { should have_content("An additional comment in reply") }
- it { within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder + .notes_holder") { should have_css(".note", count: 2) } }
-
- # removed form after reply
- it { within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder + .notes_holder") { should have_no_css("form") } }
- it { within("#4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184.line_holder + .notes_holder") { should have_link("Reply") } }
- end
-end
-
-
-
-describe "On merge request discussion", js: true do
- describe "with merge request diff note"
- describe "with commit note"
- describe "with commit diff note"
-end
diff --git a/spec/requests/notes_on_wall_spec.rb b/spec/requests/notes_on_wall_spec.rb
deleted file mode 100644
index 4adcf74e0b6..00000000000
--- a/spec/requests/notes_on_wall_spec.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-require 'spec_helper'
-
-describe "On the project wall", js: true do
- let!(:project) { create(:project) }
- let!(:commit) { project.repository.commit("bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a") }
-
- before do
- login_as :user
- project.team << [@user, :master]
- visit wall_project_path(project)
- end
-
- subject { page }
-
- describe "the note form" do
- # main target form creation
- it { should have_css(".js-main-target-form", visible: true, count: 1) }
-
- # button initalization
- it { within(".js-main-target-form") { should have_button("Add Comment") } }
- it { within(".js-main-target-form") { should_not have_link("Cancel") } }
-
- # notifiactions
- it { within(".js-main-target-form") { should have_checked_field("Notify team via email") } }
- it { within(".js-main-target-form") { should_not have_checked_field("Notify commit author") } }
- it { within(".js-main-target-form") { should_not have_unchecked_field("Notify commit author") } }
-
- describe "without text" do
- it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) } }
- end
-
- describe "with text" do
- before do
- within(".js-main-target-form") do
- fill_in "note[note]", with: "This is awesome"
- end
- end
-
- it { within(".js-main-target-form") { should_not have_css(".js-comment-button[disabled]") } }
-
- it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: true) } }
- end
-
- describe "with preview" do
- before do
- within(".js-main-target-form") do
- fill_in "note[note]", with: "This is awesome"
- find(".js-note-preview-button").trigger("click")
- end
- end
-
- it { within(".js-main-target-form") { should have_css(".js-note-preview", text: "This is awesome", visible: true) } }
-
- it { within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) } }
- it { within(".js-main-target-form") { should have_css(".js-note-edit-button", visible: true) } }
- end
- end
-
- describe "when posting a note" do
- before do
- within(".js-main-target-form") do
- fill_in "note[note]", with: "This is awsome!"
- find(".js-note-preview-button").trigger("click")
- click_button "Add Comment"
- end
- end
-
- # note added
- it { within(".js-main-target-form") { should have_content("This is awsome!") } }
-
- # reset form
- it { within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") } }
-
- # return from preview
- it { within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) } }
- it { within(".js-main-target-form") { should have_css(".js-note-text", visible: true) } }
-
-
- it "should be removable" do
- find(".js-note-delete").trigger("click")
-
- should_not have_css(".note")
- end
- end
-end
diff --git a/spec/requests/projects_deploy_keys_spec.rb b/spec/requests/projects_deploy_keys_spec.rb
deleted file mode 100644
index 25b1da9ebd8..00000000000
--- a/spec/requests/projects_deploy_keys_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'spec_helper'
-
-describe "Projects", "DeployKeys" do
- let(:project) { create(:project) }
-
- before do
- login_as :user
- project.team << [@user, :master]
- end
-
- describe "GET /keys" do
- before do
- @key = create(:key, project: project)
- visit project_deploy_keys_path(project)
- end
-
- subject { page }
-
- it { should have_content(@key.title) }
-
- describe "Destroy" do
- before { visit project_deploy_key_path(project, @key) }
-
- it "should remove entry" do
- expect {
- click_link "Remove"
- }.to change { project.deploy_keys.count }.by(-1)
- end
- end
- end
-
- describe "New key" do
- before do
- visit project_deploy_keys_path(project)
- click_link "New Deploy Key"
- end
-
- it "should open new key popup" do
- page.should have_content("New Deploy key")
- end
-
- describe "fill in" do
- before do
- fill_in "key_title", with: "laptop"
- fill_in "key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
- end
-
- it { expect { click_button "Save" }.to change {Key.count}.by(1) }
-
- it "should add new key to table" do
- click_button "Save"
-
- page.should have_content "laptop"
- end
- end
- end
-
- describe "Show page" do
- before do
- @key = create(:key, project: project)
- visit project_deploy_key_path(project, @key)
- end
-
- it { page.should have_content @key.title }
- it { page.should have_content @key.key[0..10] }
- end
-end
diff --git a/spec/requests/security/project_access_spec.rb b/spec/requests/security/project_access_spec.rb
deleted file mode 100644
index a35175102ec..00000000000
--- a/spec/requests/security/project_access_spec.rb
+++ /dev/null
@@ -1,243 +0,0 @@
-require 'spec_helper'
-
-describe "Application access" do
- describe "GET /" do
- it { root_path.should be_allowed_for :admin }
- it { root_path.should be_allowed_for :user }
- it { root_path.should be_denied_for :visitor }
- end
-
- describe "GET /projects/new" do
- it { new_project_path.should be_allowed_for :admin }
- it { new_project_path.should be_allowed_for :user }
- it { new_project_path.should be_denied_for :visitor }
- end
-
- describe "Project" do
- let(:project) { create(:project) }
-
- let(:master) { create(:user) }
- let(:guest) { create(:user) }
- let(:reporter) { create(:user) }
-
- before do
- # full access
- project.team << [master, :master]
-
- # readonly
- project.team << [reporter, :reporter]
- end
-
- describe "GET /project_code" do
- subject { project_path(project) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/tree/master" do
- subject { project_tree_path(project, project.repository.root_ref) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/commits/master" do
- subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/commit/:sha" do
- subject { project_commit_path(project, project.repository.commit) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/compare" do
- subject { project_compare_index_path(project) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/team" do
- subject { project_team_index_path(project) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/wall" do
- subject { wall_project_path(project) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/blob" do
- before do
- commit = project.repository.commit
- path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name
- @blob_path = project_blob_path(project, File.join(commit.id, path))
- end
-
- it { @blob_path.should be_allowed_for master }
- it { @blob_path.should be_allowed_for reporter }
- it { @blob_path.should be_denied_for :admin }
- it { @blob_path.should be_denied_for guest }
- it { @blob_path.should be_denied_for :user }
- it { @blob_path.should be_denied_for :visitor }
- end
-
- describe "GET /project_code/edit" do
- subject { edit_project_path(project) }
-
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/deploy_keys" do
- subject { project_deploy_keys_path(project) }
-
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/issues" do
- subject { project_issues_path(project) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/snippets" do
- subject { project_snippets_path(project) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/merge_requests" do
- subject { project_merge_requests_path(project) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/repository" do
- subject { project_repository_path(project) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/repository/branches" do
- subject { branches_project_repository_path(project) }
-
- before do
- # Speed increase
- Project.any_instance.stub(:branches).and_return([])
- end
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/repository/tags" do
- subject { tags_project_repository_path(project) }
-
- before do
- # Speed increase
- Project.any_instance.stub(:tags).and_return([])
- end
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/hooks" do
- subject { project_hooks_path(project) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /project_code/files" do
- subject { files_project_path(project) }
-
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_denied_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/requests/snippets_spec.rb b/spec/requests/snippets_spec.rb
deleted file mode 100644
index 770e34dc07c..00000000000
--- a/spec/requests/snippets_spec.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-require 'spec_helper'
-
-describe "Snippets" do
- let(:project) { create(:project) }
-
- before do
- login_as :user
- project.team << [@user, :developer]
- end
-
- describe "GET /snippets" do
- before do
- @snippet = create(:snippet,
- author: @user,
- project: project)
-
- visit project_snippets_path(project)
- end
-
- subject { page }
-
- it { should have_content(@snippet.title[0..10]) }
- it { should have_content(@snippet.project.name) }
-
- describe "Destroy" do
- before do
- # admin access to remove snippet
- @user.users_projects.destroy_all
- project.team << [@user, :master]
- visit edit_project_snippet_path(project, @snippet)
- end
-
- it "should remove entry" do
- expect {
- click_link "destroy_snippet_#{@snippet.id}"
- }.to change { Snippet.count }.by(-1)
- end
- end
- end
-
- describe "New snippet" do
- before do
- visit project_snippets_path(project)
- click_link "New Snippet"
- end
-
- it "should open new snippet popup" do
- page.current_path.should == new_project_snippet_path(project)
- end
-
- describe "fill in", js: true do
- before do
- fill_in "snippet_title", with: "login function"
- fill_in "snippet_file_name", with: "test.rb"
- page.execute_script("editor.insert('def login; end');")
- end
-
- it { expect { click_button "Save" }.to change {Snippet.count}.by(1) }
-
- it "should add new snippet to table" do
- click_button "Save"
- page.current_path.should == project_snippet_path(project, Snippet.last)
- page.should have_content "login function"
- page.should have_content "test.rb"
- end
- end
- end
-
- describe "Edit snippet" do
- before do
- @snippet = create(:snippet,
- author: @user,
- project: project)
- visit project_snippet_path(project, @snippet)
- click_link "Edit"
- end
-
- it "should open edit page" do
- page.current_path.should == edit_project_snippet_path(project, @snippet)
- end
-
- describe "fill in" do
- before do
- fill_in "snippet_title", with: "login function"
- fill_in "snippet_file_name", with: "test.rb"
- end
-
- it { expect { click_button "Save" }.to_not change {Snippet.count} }
-
- it "should update snippet fields" do
- click_button "Save"
-
- page.current_path.should == project_snippet_path(project, @snippet)
- page.should have_content "login function"
- page.should have_content "test.rb"
- end
- end
- end
-end
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index 3e0e4bb3883..7fe18ff47c3 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -56,60 +56,23 @@ describe Admin::UsersController, "routing" do
end
end
-# team_admin_project GET /admin/projects/:id/team(.:format) admin/projects#team {:id=>/[^\/]+/}
-# team_update_admin_project PUT /admin/projects/:id/team_update(.:format) admin/projects#team_update {:id=>/[^\/]+/}
-# admin_projects GET /admin/projects(.:format) admin/projects#index {:id=>/[^\/]+/}
-# POST /admin/projects(.:format) admin/projects#create {:id=>/[^\/]+/}
-# new_admin_project GET /admin/projects/new(.:format) admin/projects#new {:id=>/[^\/]+/}
-# edit_admin_project GET /admin/projects/:id/edit(.:format) admin/projects#edit {:id=>/[^\/]+/}
-# admin_project GET /admin/projects/:id(.:format) admin/projects#show {:id=>/[^\/]+/}
-# PUT /admin/projects/:id(.:format) admin/projects#update {:id=>/[^\/]+/}
-# DELETE /admin/projects/:id(.:format) admin/projects#destroy {:id=>/[^\/]+/}
+# team_admin_project GET /admin/projects/:id/team(.:format) admin/projects#team {id: /[^\/]+/}
+# team_update_admin_project PUT /admin/projects/:id/team_update(.:format) admin/projects#team_update {id: /[^\/]+/}
+# admin_projects GET /admin/projects(.:format) admin/projects#index {id: /[^\/]+/}
+# POST /admin/projects(.:format) admin/projects#create {id: /[^\/]+/}
+# new_admin_project GET /admin/projects/new(.:format) admin/projects#new {id: /[^\/]+/}
+# edit_admin_project GET /admin/projects/:id/edit(.:format) admin/projects#edit {id: /[^\/]+/}
+# admin_project GET /admin/projects/:id(.:format) admin/projects#show {id: /[^\/]+/}
+# PUT /admin/projects/:id(.:format) admin/projects#update {id: /[^\/]+/}
+# DELETE /admin/projects/:id(.:format) admin/projects#destroy {id: /[^\/]+/}
describe Admin::ProjectsController, "routing" do
- it "to #team" do
- get("/admin/projects/gitlab/team").should route_to('admin/projects#team', id: 'gitlab')
- end
-
- it "to #team_update" do
- put("/admin/projects/gitlab/team_update").should route_to('admin/projects#team_update', id: 'gitlab')
- end
-
it "to #index" do
get("/admin/projects").should route_to('admin/projects#index')
end
- it "to #edit" do
- get("/admin/projects/gitlab/edit").should route_to('admin/projects#edit', id: 'gitlab')
- end
-
it "to #show" do
get("/admin/projects/gitlab").should route_to('admin/projects#show', id: 'gitlab')
end
-
- it "to #update" do
- put("/admin/projects/gitlab").should route_to('admin/projects#update', id: 'gitlab')
- end
-
- it "to #destroy" do
- delete("/admin/projects/gitlab").should route_to('admin/projects#destroy', id: 'gitlab')
- end
-end
-
-# edit_admin_project_member GET /admin/projects/:project_id/members/:id/edit(.:format) admin/projects/members#edit {:id=>/[^\/]+/, :project_id=>/[^\/]+/}
-# admin_project_member PUT /admin/projects/:project_id/members/:id(.:format) admin/projects/members#update {:id=>/[^\/]+/, :project_id=>/[^\/]+/}
-# DELETE /admin/projects/:project_id/members/:id(.:format) admin/projects/members#destroy {:id=>/[^\/]+/, :project_id=>/[^\/]+/}
-describe Admin::Projects::MembersController, "routing" do
- it "to #edit" do
- get("/admin/projects/test/members/1/edit").should route_to('admin/projects/members#edit', project_id: 'test', id: '1')
- end
-
- it "to #update" do
- put("/admin/projects/test/members/1").should route_to('admin/projects/members#update', project_id: 'test', id: '1')
- end
-
- it "to #destroy" do
- delete("/admin/projects/test/members/1").should route_to('admin/projects/members#destroy', project_id: 'test', id: '1')
- end
end
# admin_hook_test GET /admin/hooks/:hook_id/test(.:format) admin/hooks#test
@@ -142,10 +105,10 @@ describe Admin::LogsController, "routing" do
end
end
-# admin_resque GET /admin/resque(.:format) admin/resque#show
-describe Admin::ResqueController, "routing" do
+# admin_background_jobs GET /admin/background_jobs(.:format) admin/background_jobs#show
+describe Admin::BackgroundJobsController, "routing" do
it "to #show" do
- get("/admin/resque").should route_to('admin/resque#show')
+ get("/admin/background_jobs").should route_to('admin/background_jobs#show')
end
end
diff --git a/spec/routing/notifications_routing_spec.rb b/spec/routing/notifications_routing_spec.rb
new file mode 100644
index 00000000000..112b825e023
--- /dev/null
+++ b/spec/routing/notifications_routing_spec.rb
@@ -0,0 +1,13 @@
+require "spec_helper"
+
+describe Profiles::NotificationsController do
+ describe "routing" do
+ it "routes to #show" do
+ get("/profile/notifications").should route_to("profiles/notifications#show")
+ end
+
+ it "routes to #update" do
+ put("/profile/notifications").should route_to("profiles/notifications#update")
+ end
+ end
+end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 9cf5d91349f..20c04e94c24 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -25,38 +25,38 @@ shared_examples "RESTful project resources" do
let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] }
it "to #index" do
- get("/gitlabhq/#{controller}").should route_to("#{controller}#index", project_id: 'gitlabhq') if actions.include?(:index)
+ get("/gitlab/gitlabhq/#{controller}").should route_to("projects/#{controller}#index", project_id: 'gitlab/gitlabhq') if actions.include?(:index)
end
it "to #create" do
- post("/gitlabhq/#{controller}").should route_to("#{controller}#create", project_id: 'gitlabhq') if actions.include?(:create)
+ post("/gitlab/gitlabhq/#{controller}").should route_to("projects/#{controller}#create", project_id: 'gitlab/gitlabhq') if actions.include?(:create)
end
it "to #new" do
- get("/gitlabhq/#{controller}/new").should route_to("#{controller}#new", project_id: 'gitlabhq') if actions.include?(:new)
+ get("/gitlab/gitlabhq/#{controller}/new").should route_to("projects/#{controller}#new", project_id: 'gitlab/gitlabhq') if actions.include?(:new)
end
it "to #edit" do
- get("/gitlabhq/#{controller}/1/edit").should route_to("#{controller}#edit", project_id: 'gitlabhq', id: '1') if actions.include?(:edit)
+ get("/gitlab/gitlabhq/#{controller}/1/edit").should route_to("projects/#{controller}#edit", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:edit)
end
it "to #show" do
- get("/gitlabhq/#{controller}/1").should route_to("#{controller}#show", project_id: 'gitlabhq', id: '1') if actions.include?(:show)
+ get("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#show", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:show)
end
it "to #update" do
- put("/gitlabhq/#{controller}/1").should route_to("#{controller}#update", project_id: 'gitlabhq', id: '1') if actions.include?(:update)
+ put("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#update", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:update)
end
it "to #destroy" do
- delete("/gitlabhq/#{controller}/1").should route_to("#{controller}#destroy", project_id: 'gitlabhq', id: '1') if actions.include?(:destroy)
+ delete("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#destroy", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:destroy)
end
end
# projects POST /projects(.:format) projects#create
# new_project GET /projects/new(.:format) projects#new
+# fork_project POST /:id/fork(.:format) projects#fork
# wall_project GET /:id/wall(.:format) projects#wall
-# graph_project GET /:id/graph(.:format) projects#graph
# files_project GET /:id/files(.:format) projects#files
# edit_project GET /:id/edit(.:format) projects#edit
# project GET /:id(.:format) projects#show
@@ -71,48 +71,48 @@ describe ProjectsController, "routing" do
get("/projects/new").should route_to('projects#new')
end
- it "to #wall" do
- get("/gitlabhq/wall").should route_to('projects#wall', id: 'gitlabhq')
+ it "to #fork" do
+ post("/gitlab/gitlabhq/fork").should route_to('projects#fork', id: 'gitlab/gitlabhq')
end
- it "to #graph" do
- get("/gitlabhq/graph/master").should route_to('graph#show', project_id: 'gitlabhq', id: 'master')
+ it "to #wall" do
+ get("/gitlab/gitlabhq/wall").should route_to('projects/walls#show', project_id: 'gitlab/gitlabhq')
end
- it "to #files" do
- get("/gitlabhq/files").should route_to('projects#files', id: 'gitlabhq')
+ it "to #edit" do
+ get("/gitlab/gitlabhq/edit").should route_to('projects#edit', id: 'gitlab/gitlabhq')
end
- it "to #edit" do
- get("/gitlabhq/edit").should route_to('projects#edit', id: 'gitlabhq')
+ it "to #autocomplete_sources" do
+ get('/gitlab/gitlabhq/autocomplete_sources').should route_to('projects#autocomplete_sources', id: "gitlab/gitlabhq")
end
it "to #show" do
- get("/gitlabhq").should route_to('projects#show', id: 'gitlabhq')
+ get("/gitlab/gitlabhq").should route_to('projects#show', id: 'gitlab/gitlabhq')
end
it "to #update" do
- put("/gitlabhq").should route_to('projects#update', id: 'gitlabhq')
+ put("/gitlab/gitlabhq").should route_to('projects#update', id: 'gitlab/gitlabhq')
end
it "to #destroy" do
- delete("/gitlabhq").should route_to('projects#destroy', id: 'gitlabhq')
+ delete("/gitlab/gitlabhq").should route_to('projects#destroy', id: 'gitlab/gitlabhq')
end
end
-# pages_project_wikis GET /:project_id/wikis/pages(.:format) wikis#pages
-# history_project_wiki GET /:project_id/wikis/:id/history(.:format) wikis#history
-# project_wikis POST /:project_id/wikis(.:format) wikis#create
-# edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) wikis#edit
-# project_wiki GET /:project_id/wikis/:id(.:format) wikis#show
-# DELETE /:project_id/wikis/:id(.:format) wikis#destroy
-describe WikisController, "routing" do
+# pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages
+# history_project_wiki GET /:project_id/wikis/:id/history(.:format) projects/wikis#history
+# project_wikis POST /:project_id/wikis(.:format) projects/wikis#create
+# edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) projects/wikis#edit
+# project_wiki GET /:project_id/wikis/:id(.:format) projects/wikis#show
+# DELETE /:project_id/wikis/:id(.:format) projects/wikis#destroy
+describe Projects::WikisController, "routing" do
it "to #pages" do
- get("/gitlabhq/wikis/pages").should route_to('wikis#pages', project_id: 'gitlabhq')
+ get("/gitlab/gitlabhq/wikis/pages").should route_to('projects/wikis#pages', project_id: 'gitlab/gitlabhq')
end
it "to #history" do
- get("/gitlabhq/wikis/1/history").should route_to('wikis#history', project_id: 'gitlabhq', id: '1')
+ get("/gitlab/gitlabhq/wikis/1/history").should route_to('projects/wikis#history', project_id: 'gitlab/gitlabhq', id: '1')
end
it_behaves_like "RESTful project resources" do
@@ -121,53 +121,33 @@ describe WikisController, "routing" do
end
end
-# branches_project_repository GET /:project_id/repository/branches(.:format) repositories#branches
-# tags_project_repository GET /:project_id/repository/tags(.:format) repositories#tags
-# archive_project_repository GET /:project_id/repository/archive(.:format) repositories#archive
-# project_repository POST /:project_id/repository(.:format) repositories#create
-# new_project_repository GET /:project_id/repository/new(.:format) repositories#new
-# edit_project_repository GET /:project_id/repository/edit(.:format) repositories#edit
-# GET /:project_id/repository(.:format) repositories#show
-# PUT /:project_id/repository(.:format) repositories#update
-# DELETE /:project_id/repository(.:format) repositories#destroy
-describe RepositoriesController, "routing" do
- it "to #branches" do
- get("/gitlabhq/repository/branches").should route_to('repositories#branches', project_id: 'gitlabhq')
- end
-
- it "to #tags" do
- get("/gitlabhq/repository/tags").should route_to('repositories#tags', project_id: 'gitlabhq')
- end
-
+# branches_project_repository GET /:project_id/repository/branches(.:format) projects/repositories#branches
+# tags_project_repository GET /:project_id/repository/tags(.:format) projects/repositories#tags
+# archive_project_repository GET /:project_id/repository/archive(.:format) projects/repositories#archive
+# edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit
+describe Projects::RepositoriesController, "routing" do
it "to #archive" do
- get("/gitlabhq/repository/archive").should route_to('repositories#archive', project_id: 'gitlabhq')
- end
-
- it "to #create" do
- post("/gitlabhq/repository").should route_to('repositories#create', project_id: 'gitlabhq')
- end
-
- it "to #new" do
- get("/gitlabhq/repository/new").should route_to('repositories#new', project_id: 'gitlabhq')
- end
-
- it "to #edit" do
- get("/gitlabhq/repository/edit").should route_to('repositories#edit', project_id: 'gitlabhq')
+ get("/gitlab/gitlabhq/repository/archive").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq')
end
it "to #show" do
- get("/gitlabhq/repository").should route_to('repositories#show', project_id: 'gitlabhq')
+ get("/gitlab/gitlabhq/repository").should route_to('projects/repositories#show', project_id: 'gitlab/gitlabhq')
end
+end
- it "to #update" do
- put("/gitlabhq/repository").should route_to('repositories#update', project_id: 'gitlabhq')
+describe Projects::BranchesController, "routing" do
+ it "to #branches" do
+ get("/gitlab/gitlabhq/branches").should route_to('projects/branches#index', project_id: 'gitlab/gitlabhq')
end
+end
- it "to #destroy" do
- delete("/gitlabhq/repository").should route_to('repositories#destroy', project_id: 'gitlabhq')
+describe Projects::TagsController, "routing" do
+ it "to #tags" do
+ get("/gitlab/gitlabhq/tags").should route_to('projects/tags#index', project_id: 'gitlab/gitlabhq')
end
end
+
# project_deploy_keys GET /:project_id/deploy_keys(.:format) deploy_keys#index
# POST /:project_id/deploy_keys(.:format) deploy_keys#create
# new_project_deploy_key GET /:project_id/deploy_keys/new(.:format) deploy_keys#new
@@ -175,7 +155,7 @@ end
# project_deploy_key GET /:project_id/deploy_keys/:id(.:format) deploy_keys#show
# PUT /:project_id/deploy_keys/:id(.:format) deploy_keys#update
# DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy
-describe DeployKeysController, "routing" do
+describe Projects::DeployKeysController, "routing" do
it_behaves_like "RESTful project resources" do
let(:controller) { 'deploy_keys' }
end
@@ -184,7 +164,7 @@ end
# project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index
# POST /:project_id/protected_branches(.:format) protected_branches#create
# project_protected_branch DELETE /:project_id/protected_branches/:id(.:format) protected_branches#destroy
-describe ProtectedBranchesController, "routing" do
+describe Projects::ProtectedBranchesController, "routing" do
it_behaves_like "RESTful project resources" do
let(:actions) { [:index, :create, :destroy] }
let(:controller) { 'protected_branches' }
@@ -194,53 +174,58 @@ end
# switch_project_refs GET /:project_id/refs/switch(.:format) refs#switch
# logs_tree_project_ref GET /:project_id/refs/:id/logs_tree(.:format) refs#logs_tree
# logs_file_project_ref GET /:project_id/refs/:id/logs_tree/:path(.:format) refs#logs_tree
-describe RefsController, "routing" do
+describe Projects::RefsController, "routing" do
it "to #switch" do
- get("/gitlabhq/refs/switch").should route_to('refs#switch', project_id: 'gitlabhq')
+ get("/gitlab/gitlabhq/refs/switch").should route_to('projects/refs#switch', project_id: 'gitlab/gitlabhq')
end
it "to #logs_tree" do
- get("/gitlabhq/refs/stable/logs_tree").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable')
- get("/gitlabhq/refs/stable/logs_tree/foo/bar/baz").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz')
+ get("/gitlab/gitlabhq/refs/stable/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable')
+ get("/gitlab/gitlabhq/refs/feature%2345/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45')
+ get("/gitlab/gitlabhq/refs/feature%2B45/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45')
+ get("/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'foo/bar/baz')
+ get("/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45', path: 'foo/bar/baz')
+ get("/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45', path: 'foo/bar/baz')
+ get("/gitlab/gitlabhq/refs/stable/logs_tree/files.scss").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'files.scss')
end
end
-# diffs_project_merge_request GET /:project_id/merge_requests/:id/diffs(.:format) merge_requests#diffs
-# automerge_project_merge_request GET /:project_id/merge_requests/:id/automerge(.:format) merge_requests#automerge
-# automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) merge_requests#automerge_check
-# branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) merge_requests#branch_from
-# branch_to_project_merge_requests GET /:project_id/merge_requests/branch_to(.:format) merge_requests#branch_to
-# project_merge_requests GET /:project_id/merge_requests(.:format) merge_requests#index
-# POST /:project_id/merge_requests(.:format) merge_requests#create
-# new_project_merge_request GET /:project_id/merge_requests/new(.:format) merge_requests#new
-# edit_project_merge_request GET /:project_id/merge_requests/:id/edit(.:format) merge_requests#edit
-# project_merge_request GET /:project_id/merge_requests/:id(.:format) merge_requests#show
-# PUT /:project_id/merge_requests/:id(.:format) merge_requests#update
-# DELETE /:project_id/merge_requests/:id(.:format) merge_requests#destroy
-describe MergeRequestsController, "routing" do
+# diffs_project_merge_request GET /:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs
+# automerge_project_merge_request GET /:project_id/merge_requests/:id/automerge(.:format) projects/merge_requests#automerge
+# automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check
+# branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from
+# branch_to_project_merge_requests GET /:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to
+# project_merge_requests GET /:project_id/merge_requests(.:format) projects/merge_requests#index
+# POST /:project_id/merge_requests(.:format) projects/merge_requests#create
+# new_project_merge_request GET /:project_id/merge_requests/new(.:format) projects/merge_requests#new
+# edit_project_merge_request GET /:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit
+# project_merge_request GET /:project_id/merge_requests/:id(.:format) projects/merge_requests#show
+# PUT /:project_id/merge_requests/:id(.:format) projects/merge_requests#update
+# DELETE /:project_id/merge_requests/:id(.:format) projects/merge_requests#destroy
+describe Projects::MergeRequestsController, "routing" do
it "to #diffs" do
- get("/gitlabhq/merge_requests/1/diffs").should route_to('merge_requests#diffs', project_id: 'gitlabhq', id: '1')
+ get("/gitlab/gitlabhq/merge_requests/1/diffs").should route_to('projects/merge_requests#diffs', project_id: 'gitlab/gitlabhq', id: '1')
end
it "to #automerge" do
- get("/gitlabhq/merge_requests/1/automerge").should route_to('merge_requests#automerge', project_id: 'gitlabhq', id: '1')
+ get("/gitlab/gitlabhq/merge_requests/1/automerge").should route_to('projects/merge_requests#automerge', project_id: 'gitlab/gitlabhq', id: '1')
end
it "to #automerge_check" do
- get("/gitlabhq/merge_requests/1/automerge_check").should route_to('merge_requests#automerge_check', project_id: 'gitlabhq', id: '1')
+ get("/gitlab/gitlabhq/merge_requests/1/automerge_check").should route_to('projects/merge_requests#automerge_check', project_id: 'gitlab/gitlabhq', id: '1')
end
it "to #branch_from" do
- get("/gitlabhq/merge_requests/branch_from").should route_to('merge_requests#branch_from', project_id: 'gitlabhq')
+ get("/gitlab/gitlabhq/merge_requests/branch_from").should route_to('projects/merge_requests#branch_from', project_id: 'gitlab/gitlabhq')
end
it "to #branch_to" do
- get("/gitlabhq/merge_requests/branch_to").should route_to('merge_requests#branch_to', project_id: 'gitlabhq')
+ get("/gitlab/gitlabhq/merge_requests/branch_to").should route_to('projects/merge_requests#branch_to', project_id: 'gitlab/gitlabhq')
end
it "to #show" do
- get("/gitlabhq/merge_requests/1.diff").should route_to('merge_requests#show', project_id: 'gitlabhq', id: '1', format: 'diff')
- get("/gitlabhq/merge_requests/1.patch").should route_to('merge_requests#show', project_id: 'gitlabhq', id: '1', format: 'patch')
+ get("/gitlab/gitlabhq/merge_requests/1.diff").should route_to('projects/merge_requests#show', project_id: 'gitlab/gitlabhq', id: '1', format: 'diff')
+ get("/gitlab/gitlabhq/merge_requests/1.patch").should route_to('projects/merge_requests#show', project_id: 'gitlab/gitlabhq', id: '1', format: 'patch')
end
it_behaves_like "RESTful project resources" do
@@ -259,11 +244,35 @@ end
# DELETE /:project_id/snippets/:id(.:format) snippets#destroy
describe SnippetsController, "routing" do
it "to #raw" do
- get("/gitlabhq/snippets/1/raw").should route_to('snippets#raw', project_id: 'gitlabhq', id: '1')
+ get("/gitlab/gitlabhq/snippets/1/raw").should route_to('projects/snippets#raw', project_id: 'gitlab/gitlabhq', id: '1')
end
- it_behaves_like "RESTful project resources" do
- let(:controller) { 'snippets' }
+ it "to #index" do
+ get("/gitlab/gitlabhq/snippets").should route_to("projects/snippets#index", project_id: 'gitlab/gitlabhq')
+ end
+
+ it "to #create" do
+ post("/gitlab/gitlabhq/snippets").should route_to("projects/snippets#create", project_id: 'gitlab/gitlabhq')
+ end
+
+ it "to #new" do
+ get("/gitlab/gitlabhq/snippets/new").should route_to("projects/snippets#new", project_id: 'gitlab/gitlabhq')
+ end
+
+ it "to #edit" do
+ get("/gitlab/gitlabhq/snippets/1/edit").should route_to("projects/snippets#edit", project_id: 'gitlab/gitlabhq', id: '1')
+ end
+
+ it "to #show" do
+ get("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#show", project_id: 'gitlab/gitlabhq', id: '1')
+ end
+
+ it "to #update" do
+ put("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#update", project_id: 'gitlab/gitlabhq', id: '1')
+ end
+
+ it "to #destroy" do
+ delete("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#destroy", project_id: 'gitlab/gitlabhq', id: '1')
end
end
@@ -271,9 +280,9 @@ end
# project_hooks GET /:project_id/hooks(.:format) hooks#index
# POST /:project_id/hooks(.:format) hooks#create
# project_hook DELETE /:project_id/hooks/:id(.:format) hooks#destroy
-describe HooksController, "routing" do
+describe Projects::HooksController, "routing" do
it "to #test" do
- get("/gitlabhq/hooks/1/test").should route_to('hooks#test', project_id: 'gitlabhq', id: '1')
+ get("/gitlab/gitlabhq/hooks/1/test").should route_to('projects/hooks#test', project_id: 'gitlab/gitlabhq', id: '1')
end
it_behaves_like "RESTful project resources" do
@@ -282,13 +291,13 @@ describe HooksController, "routing" do
end
end
-# project_commit GET /:project_id/commit/:id(.:format) commit#show {:id=>/[[:alnum:]]{6,40}/, :project_id=>/[^\/]+/}
-describe CommitController, "routing" do
+# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /[[:alnum:]]{6,40}/, project_id: /[^\/]+/}
+describe Projects::CommitController, "routing" do
it "to #show" do
- get("/gitlabhq/commit/4246fb").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fb')
- get("/gitlabhq/commit/4246fb.diff").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fb', format: 'diff')
- get("/gitlabhq/commit/4246fb.patch").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fb', format: 'patch')
- get("/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5')
+ get("/gitlab/gitlabhq/commit/4246fb").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb')
+ get("/gitlab/gitlabhq/commit/4246fb.diff").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb', format: 'diff')
+ get("/gitlab/gitlabhq/commit/4246fb.patch").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb', format: 'patch')
+ get("/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5')
end
end
@@ -296,11 +305,15 @@ end
# project_commits GET /:project_id/commits(.:format) commits#index
# POST /:project_id/commits(.:format) commits#create
# project_commit GET /:project_id/commits/:id(.:format) commits#show
-describe CommitsController, "routing" do
+describe Projects::CommitsController, "routing" do
it_behaves_like "RESTful project resources" do
let(:actions) { [:show] }
let(:controller) { 'commits' }
end
+
+ it "to #show" do
+ get("/gitlab/gitlabhq/commits/master.atom").should route_to('projects/commits#show', project_id: 'gitlab/gitlabhq', id: "master", format: "atom")
+ end
end
# project_team_members GET /:project_id/team_members(.:format) team_members#index
@@ -310,8 +323,9 @@ end
# project_team_member GET /:project_id/team_members/:id(.:format) team_members#show
# PUT /:project_id/team_members/:id(.:format) team_members#update
# DELETE /:project_id/team_members/:id(.:format) team_members#destroy
-describe TeamMembersController, "routing" do
+describe Projects::TeamMembersController, "routing" do
it_behaves_like "RESTful project resources" do
+ let(:actions) { [:new, :create, :update, :destroy] }
let(:controller) { 'team_members' }
end
end
@@ -323,7 +337,7 @@ end
# project_milestone GET /:project_id/milestones/:id(.:format) milestones#show
# PUT /:project_id/milestones/:id(.:format) milestones#update
# DELETE /:project_id/milestones/:id(.:format) milestones#destroy
-describe MilestonesController, "routing" do
+describe Projects::MilestonesController, "routing" do
it_behaves_like "RESTful project resources" do
let(:controller) { 'milestones' }
let(:actions) { [:index, :create, :new, :edit, :show, :update] }
@@ -331,9 +345,9 @@ describe MilestonesController, "routing" do
end
# project_labels GET /:project_id/labels(.:format) labels#index
-describe LabelsController, "routing" do
+describe Projects::LabelsController, "routing" do
it "to #index" do
- get("/gitlabhq/labels").should route_to('labels#index', project_id: 'gitlabhq')
+ get("/gitlab/gitlabhq/labels").should route_to('projects/labels#index', project_id: 'gitlab/gitlabhq')
end
end
@@ -347,17 +361,9 @@ end
# project_issue GET /:project_id/issues/:id(.:format) issues#show
# PUT /:project_id/issues/:id(.:format) issues#update
# DELETE /:project_id/issues/:id(.:format) issues#destroy
-describe IssuesController, "routing" do
- it "to #sort" do
- post("/gitlabhq/issues/sort").should route_to('issues#sort', project_id: 'gitlabhq')
- end
-
+describe Projects::IssuesController, "routing" do
it "to #bulk_update" do
- post("/gitlabhq/issues/bulk_update").should route_to('issues#bulk_update', project_id: 'gitlabhq')
- end
-
- it "to #search" do
- get("/gitlabhq/issues/search").should route_to('issues#search', project_id: 'gitlabhq')
+ post("/gitlab/gitlabhq/issues/bulk_update").should route_to('projects/issues#bulk_update', project_id: 'gitlab/gitlabhq')
end
it_behaves_like "RESTful project resources" do
@@ -370,9 +376,9 @@ end
# project_notes GET /:project_id/notes(.:format) notes#index
# POST /:project_id/notes(.:format) notes#create
# project_note DELETE /:project_id/notes/:id(.:format) notes#destroy
-describe NotesController, "routing" do
+describe Projects::NotesController, "routing" do
it "to #preview" do
- post("/gitlabhq/notes/preview").should route_to('notes#preview', project_id: 'gitlabhq')
+ post("/gitlab/gitlabhq/notes/preview").should route_to('projects/notes#preview', project_id: 'gitlab/gitlabhq')
end
it_behaves_like "RESTful project resources" do
@@ -381,42 +387,58 @@ describe NotesController, "routing" do
end
end
-# project_blame GET /:project_id/blame/:id(.:format) blame#show {:id=>/.+/, :project_id=>/[^\/]+/}
-describe BlameController, "routing" do
+# project_blame GET /:project_id/blame/:id(.:format) blame#show {id: /.+/, project_id: /[^\/]+/}
+describe Projects::BlameController, "routing" do
it "to #show" do
- get("/gitlabhq/blame/master/app/models/project.rb").should route_to('blame#show', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
+ get("/gitlab/gitlabhq/blame/master/app/models/project.rb").should route_to('projects/blame#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb')
+ get("/gitlab/gitlabhq/blame/master/files.scss").should route_to('projects/blame#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss')
end
end
-# project_blob GET /:project_id/blob/:id(.:format) blob#show {:id=>/.+/, :project_id=>/[^\/]+/}
-describe BlobController, "routing" do
+# project_blob GET /:project_id/blob/:id(.:format) blob#show {id: /.+/, project_id: /[^\/]+/}
+describe Projects::BlobController, "routing" do
it "to #show" do
- get("/gitlabhq/blob/master/app/models/project.rb").should route_to('blob#show', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
- get("/gitlabhq/blob/master/app/models/compare.rb").should route_to('blob#show', project_id: 'gitlabhq', id: 'master/app/models/compare.rb')
+ get("/gitlab/gitlabhq/blob/master/app/models/project.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb')
+ get("/gitlab/gitlabhq/blob/master/app/models/compare.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/compare.rb')
+ get("/gitlab/gitlabhq/blob/master/files.scss").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss')
end
end
-# project_tree GET /:project_id/tree/:id(.:format) tree#show {:id=>/.+/, :project_id=>/[^\/]+/}
-describe TreeController, "routing" do
+# project_tree GET /:project_id/tree/:id(.:format) tree#show {id: /.+/, project_id: /[^\/]+/}
+describe Projects::TreeController, "routing" do
it "to #show" do
- get("/gitlabhq/tree/master/app/models/project.rb").should route_to('tree#show', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
+ get("/gitlab/gitlabhq/tree/master/app/models/project.rb").should route_to('projects/tree#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb')
+ get("/gitlab/gitlabhq/tree/master/files.scss").should route_to('projects/tree#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss')
end
end
-# project_compare_index GET /:project_id/compare(.:format) compare#index {:id=>/[^\/]+/, :project_id=>/[^\/]+/}
-# POST /:project_id/compare(.:format) compare#create {:id=>/[^\/]+/, :project_id=>/[^\/]+/}
-# project_compare /:project_id/compare/:from...:to(.:format) compare#show {:from=>/.+/, :to=>/.+/, :id=>/[^\/]+/, :project_id=>/[^\/]+/}
-describe CompareController, "routing" do
+# project_compare_index GET /:project_id/compare(.:format) compare#index {id: /[^\/]+/, project_id: /[^\/]+/}
+# POST /:project_id/compare(.:format) compare#create {id: /[^\/]+/, project_id: /[^\/]+/}
+# project_compare /:project_id/compare/:from...:to(.:format) compare#show {from: /.+/, to: /.+/, id: /[^\/]+/, project_id: /[^\/]+/}
+describe Projects::CompareController, "routing" do
it "to #index" do
- get("/gitlabhq/compare").should route_to('compare#index', project_id: 'gitlabhq')
+ get("/gitlab/gitlabhq/compare").should route_to('projects/compare#index', project_id: 'gitlab/gitlabhq')
end
it "to #compare" do
- post("/gitlabhq/compare").should route_to('compare#create', project_id: 'gitlabhq')
+ post("/gitlab/gitlabhq/compare").should route_to('projects/compare#create', project_id: 'gitlab/gitlabhq')
end
it "to #show" do
- get("/gitlabhq/compare/master...stable").should route_to('compare#show', project_id: 'gitlabhq', from: 'master', to: 'stable')
- get("/gitlabhq/compare/issue/1234...stable").should route_to('compare#show', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable')
+ get("/gitlab/gitlabhq/compare/master...stable").should route_to('projects/compare#show', project_id: 'gitlab/gitlabhq', from: 'master', to: 'stable')
+ get("/gitlab/gitlabhq/compare/issue/1234...stable").should route_to('projects/compare#show', project_id: 'gitlab/gitlabhq', from: 'issue/1234', to: 'stable')
+ end
+end
+
+describe Projects::NetworkController, "routing" do
+ it "to #show" do
+ get("/gitlab/gitlabhq/network/master").should route_to('projects/network#show', project_id: 'gitlab/gitlabhq', id: 'master')
+ get("/gitlab/gitlabhq/network/master.json").should route_to('projects/network#show', project_id: 'gitlab/gitlabhq', id: 'master', format: "json")
+ end
+end
+
+describe Projects::GraphsController, "routing" do
+ it "to #show" do
+ get("/gitlab/gitlabhq/graphs/master").should route_to('projects/graphs#show', project_id: 'gitlab/gitlabhq', id: 'master')
end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 5ad8165ecce..946ef7c28cb 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -7,21 +7,60 @@ describe SearchController, "routing" do
end
end
-# gitlab_api /api Gitlab::API
-# resque /info/resque Resque::Server
+# gitlab_api /api API::API
# /:path Grack
describe "Mounted Apps", "routing" do
it "to API" do
- get("/api").should be_routable
+ get("/api/issues").should be_routable
end
- it "to Resque" do
- pending
- get("/info/resque").should be_routable
+ it "to Grack" do
+ get("/gitlab/gitlabhq.git").should be_routable
end
+end
- it "to Grack" do
- get("/gitlabhq.git").should be_routable
+# snippets GET /snippets(.:format) snippets#index
+# POST /snippets(.:format) snippets#create
+# new_snippet GET /snippets/new(.:format) snippets#new
+# edit_snippet GET /snippets/:id/edit(.:format) snippets#edit
+# snippet GET /snippets/:id(.:format) snippets#show
+# PUT /snippets/:id(.:format) snippets#update
+# DELETE /snippets/:id(.:format) snippets#destroy
+describe SnippetsController, "routing" do
+ it "to #user_index" do
+ get("/s/User").should route_to('snippets#user_index', username: 'User')
+ end
+
+ it "to #raw" do
+ get("/snippets/1/raw").should route_to('snippets#raw', id: '1')
+ end
+
+ it "to #index" do
+ get("/snippets").should route_to('snippets#index')
+ end
+
+ it "to #create" do
+ post("/snippets").should route_to('snippets#create')
+ end
+
+ it "to #new" do
+ get("/snippets/new").should route_to('snippets#new')
+ end
+
+ it "to #edit" do
+ get("/snippets/1/edit").should route_to('snippets#edit', id: '1')
+ end
+
+ it "to #show" do
+ get("/snippets/1").should route_to('snippets#show', id: '1')
+ end
+
+ it "to #update" do
+ put("/snippets/1").should route_to('snippets#update', id: '1')
+ end
+
+ it "to #destroy" do
+ delete("/snippets/1").should route_to('snippets#destroy', id: '1')
end
end
@@ -116,33 +155,33 @@ end
# key GET /keys/:id(.:format) keys#show
# PUT /keys/:id(.:format) keys#update
# DELETE /keys/:id(.:format) keys#destroy
-describe KeysController, "routing" do
+describe Profiles::KeysController, "routing" do
it "to #index" do
- get("/keys").should route_to('keys#index')
+ get("/profile/keys").should route_to('profiles/keys#index')
end
it "to #create" do
- post("/keys").should route_to('keys#create')
+ post("/profile/keys").should route_to('profiles/keys#create')
end
it "to #new" do
- get("/keys/new").should route_to('keys#new')
+ get("/profile/keys/new").should route_to('profiles/keys#new')
end
it "to #edit" do
- get("/keys/1/edit").should route_to('keys#edit', id: '1')
+ get("/profile/keys/1/edit").should route_to('profiles/keys#edit', id: '1')
end
it "to #show" do
- get("/keys/1").should route_to('keys#show', id: '1')
+ get("/profile/keys/1").should route_to('profiles/keys#show', id: '1')
end
it "to #update" do
- put("/keys/1").should route_to('keys#update', id: '1')
+ put("/profile/keys/1").should route_to('profiles/keys#update', id: '1')
end
it "to #destroy" do
- delete("/keys/1").should route_to('keys#destroy', id: '1')
+ delete("/profile/keys/1").should route_to('profiles/keys#destroy', id: '1')
end
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
new file mode 100644
index 00000000000..e3c25fa0469
--- /dev/null
+++ b/spec/services/git_push_service_spec.rb
@@ -0,0 +1,215 @@
+require 'spec_helper'
+
+describe GitPushService do
+ let (:user) { create :user }
+ let (:project) { create :project_with_code }
+ let (:service) { GitPushService.new }
+
+ before do
+ @blankrev = '0000000000000000000000000000000000000000'
+ @oldrev = 'b98a310def241a6fd9c9a9a3e7934c48e498fe81'
+ @newrev = 'b19a04f53caeebf4fe5ec2327cb83e9253dc91bb'
+ @ref = 'refs/heads/master'
+ end
+
+ describe "Git Push Data" do
+ before do
+ service.execute(project, user, @oldrev, @newrev, @ref)
+ @push_data = service.push_data
+ @commit = project.repository.commit(@newrev)
+ end
+
+ subject { @push_data }
+
+ it { should include(before: @oldrev) }
+ it { should include(after: @newrev) }
+ it { should include(ref: @ref) }
+ it { should include(user_id: user.id) }
+ it { should include(user_name: user.name) }
+
+ 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
+
+ context "with commits" do
+ subject { @push_data[:commits] }
+
+ it { should be_an(Array) }
+ it { should have(1).element }
+
+ context "the commit" do
+ subject { @push_data[:commits].first }
+
+ it { should include(id: @commit.id) }
+ it { should include(message: @commit.safe_message) }
+ it { should include(timestamp: @commit.date.xmlschema) }
+ it { should include(url: "#{Gitlab.config.gitlab.url}/#{project.to_param}/commit/#{@commit.id}") }
+
+ context "with a author" do
+ subject { @push_data[:commits].first[:author] }
+
+ it { should include(name: @commit.author_name) }
+ it { should include(email: @commit.author_email) }
+ end
+ end
+ end
+ end
+
+ describe "Push Event" do
+ before do
+ service.execute(project, user, @oldrev, @newrev, @ref)
+ @event = Event.last
+ end
+
+ it { @event.should_not be_nil }
+ it { @event.project.should == project }
+ it { @event.action.should == Event::PUSHED }
+ it { @event.data.should == service.push_data }
+ end
+
+ describe "Web Hooks" do
+ context "with web hooks" do
+ before do
+ @project_hook = create(:project_hook)
+ @project_hook_2 = create(:project_hook)
+ project.hooks << [@project_hook, @project_hook_2]
+
+ stub_request(:post, @project_hook.url)
+ stub_request(:post, @project_hook_2.url)
+ end
+
+ it "executes multiple web hook" do
+ @project_hook.should_receive(:async_execute).once
+ @project_hook_2.should_receive(:async_execute).once
+
+ service.execute(project, user, @oldrev, @newrev, @ref)
+ end
+ end
+
+ context "execute web hooks" do
+ before do
+ @project_hook = create(:project_hook)
+ project.hooks << [@project_hook]
+ stub_request(:post, @project_hook.url)
+ end
+
+ it "when pushing a branch for the first time" do
+ @project_hook.should_receive(:async_execute)
+ service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ end
+
+ it "when pushing tags" do
+ @project_hook.should_not_receive(:async_execute)
+ service.execute(project, user, 'newrev', 'newrev', 'refs/tags/v1.0.0')
+ end
+ end
+ end
+
+ describe "cross-reference notes" do
+ let(:issue) { create :issue, project: project }
+ let(:commit_author) { create :user }
+ let(:commit) { project.repository.commit }
+
+ before do
+ commit.stub({
+ safe_message: "this commit \n mentions ##{issue.id}",
+ references: [issue],
+ author_name: commit_author.name,
+ author_email: commit_author.email
+ })
+ project.repository.stub(commits_between: [commit])
+ end
+
+ it "creates a note if a pushed commit mentions an issue" do
+ Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
+
+ service.execute(project, user, @oldrev, @newrev, @ref)
+ end
+
+ it "only creates a cross-reference note if one doesn't already exist" do
+ Note.create_cross_reference_note(issue, commit, user, project)
+
+ Note.should_not_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
+
+ service.execute(project, user, @oldrev, @newrev, @ref)
+ end
+
+ it "defaults to the pushing user if the commit's author is not known" do
+ commit.stub(author_name: 'unknown name', author_email: 'unknown@email.com')
+ Note.should_receive(:create_cross_reference_note).with(issue, commit, user, project)
+
+ service.execute(project, user, @oldrev, @newrev, @ref)
+ end
+
+ it "finds references in the first push to a non-default branch" do
+ project.repository.stub(:commits_between).with(@blankrev, @newrev).and_return([])
+ project.repository.stub(:commits_between).with("master", @newrev).and_return([commit])
+
+ Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
+
+ service.execute(project, user, @blankrev, @newrev, 'refs/heads/other')
+ end
+
+ it "finds references in the first push to a default branch" do
+ project.repository.stub(:commits_between).with(@blankrev, @newrev).and_return([])
+ project.repository.stub(:commits).with(@newrev).and_return([commit])
+
+ Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
+
+ service.execute(project, user, @blankrev, @newrev, 'refs/heads/master')
+ end
+ end
+
+ describe "closing issues from pushed commits" do
+ let(:issue) { create :issue, project: project }
+ let(:other_issue) { create :issue, project: project }
+ let(:commit_author) { create :user }
+ let(:closing_commit) { project.repository.commit }
+
+ before do
+ closing_commit.stub({
+ issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/,
+ safe_message: "this is some work.\n\ncloses ##{issue.iid}",
+ author_name: commit_author.name,
+ author_email: commit_author.email
+ })
+
+ project.repository.stub(commits_between: [closing_commit])
+ end
+
+ it "closes issues with commit messages" do
+ service.execute(project, user, @oldrev, @newrev, @ref)
+
+ Issue.find(issue.id).should be_closed
+ end
+
+ it "passes the closing commit as a thread-local" do
+ service.execute(project, user, @oldrev, @newrev, @ref)
+
+ Thread.current[:current_commit].should == closing_commit
+ end
+
+ it "doesn't create cross-reference notes for a closing reference" do
+ expect {
+ service.execute(project, user, @oldrev, @newrev, @ref)
+ }.not_to change { Note.where(project_id: project.id, system: true).count }
+ end
+
+ it "doesn't close issues when pushed to non-default branches" do
+ project.stub(default_branch: 'durf')
+
+ # The push still shouldn't create cross-reference notes.
+ expect {
+ service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf')
+ }.not_to change { Note.where(project_id: project.id, system: true).count }
+
+ Issue.find(issue.id).should be_opened
+ end
+ end
+end
+
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
new file mode 100644
index 00000000000..a112835d4d0
--- /dev/null
+++ b/spec/services/notification_service_spec.rb
@@ -0,0 +1,249 @@
+require 'spec_helper'
+
+describe NotificationService do
+ let(:notification) { NotificationService.new }
+
+ describe 'Keys' do
+ describe :new_key do
+ let(:key) { create(:personal_key) }
+
+ it { notification.new_key(key).should be_true }
+
+ it 'should sent email to key owner' do
+ Notify.should_receive(:new_ssh_key_email).with(key.id)
+ notification.new_key(key)
+ end
+ end
+ end
+
+ describe 'Notes' do
+ context 'issue note' do
+ let(:issue) { create(:issue, assignee: create(:user)) }
+ let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced') }
+
+ before do
+ build_team(note.project)
+ end
+
+ describe :new_note do
+ it do
+ should_email(@u_watcher.id)
+ should_email(note.noteable.author_id)
+ should_email(note.noteable.assignee_id)
+ should_email(@u_mentioned.id)
+ should_not_email(note.author_id)
+ should_not_email(@u_participating.id)
+ should_not_email(@u_disabled.id)
+ notification.new_note(note)
+ end
+
+ def should_email(user_id)
+ Notify.should_receive(:note_issue_email).with(user_id, note.id)
+ end
+
+ def should_not_email(user_id)
+ Notify.should_not_receive(:note_issue_email).with(user_id, note.id)
+ end
+ end
+ end
+
+ context 'commit note' do
+ let(:note) { create(:note_on_commit) }
+
+ before do
+ build_team(note.project)
+ note.stub(:commit_author => @u_committer)
+ end
+
+ describe :new_note do
+ it do
+ should_email(@u_committer.id, note)
+ should_email(@u_watcher.id, note)
+ should_not_email(@u_mentioned.id, note)
+ should_not_email(note.author_id, note)
+ should_not_email(@u_participating.id, note)
+ should_not_email(@u_disabled.id, note)
+ notification.new_note(note)
+ end
+
+ it do
+ note.update_attribute(:note, '@mention referenced')
+ should_email(@u_committer.id, note)
+ should_email(@u_watcher.id, note)
+ should_email(@u_mentioned.id, note)
+ should_not_email(note.author_id, note)
+ should_not_email(@u_participating.id, note)
+ should_not_email(@u_disabled.id, note)
+ notification.new_note(note)
+ end
+
+ def should_email(user_id, n)
+ Notify.should_receive(:note_commit_email).with(user_id, n.id)
+ end
+
+ def should_not_email(user_id, n)
+ Notify.should_not_receive(:note_commit_email).with(user_id, n.id)
+ end
+ end
+ end
+ end
+
+ describe 'Issues' do
+ let(:issue) { create :issue, assignee: create(:user) }
+
+ before do
+ build_team(issue.project)
+ end
+
+ describe :new_issue do
+ it do
+ should_email(issue.assignee_id)
+ should_email(@u_watcher.id)
+ should_not_email(@u_participating.id)
+ should_not_email(@u_disabled.id)
+ notification.new_issue(issue, @u_disabled)
+ end
+
+ def should_email(user_id)
+ Notify.should_receive(:new_issue_email).with(user_id, issue.id)
+ end
+
+ def should_not_email(user_id)
+ Notify.should_not_receive(:new_issue_email).with(user_id, issue.id)
+ end
+ end
+
+ describe :reassigned_issue do
+ it 'should email new assignee' do
+ should_email(issue.assignee_id)
+ should_email(@u_watcher.id)
+ should_not_email(@u_participating.id)
+ should_not_email(@u_disabled.id)
+
+ notification.reassigned_issue(issue, @u_disabled)
+ end
+
+ def should_email(user_id)
+ Notify.should_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id)
+ end
+
+ def should_not_email(user_id)
+ Notify.should_not_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id)
+ end
+ end
+
+ describe :close_issue do
+ it 'should sent email to issue assignee and issue author' do
+ should_email(issue.assignee_id)
+ should_email(issue.author_id)
+ should_email(@u_watcher.id)
+ should_not_email(@u_participating.id)
+ should_not_email(@u_disabled.id)
+
+ notification.close_issue(issue, @u_disabled)
+ end
+
+ def should_email(user_id)
+ Notify.should_receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id)
+ end
+
+ def should_not_email(user_id)
+ Notify.should_not_receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id)
+ end
+ end
+ end
+
+ describe 'Merge Requests' do
+ let(:merge_request) { create :merge_request, assignee: create(:user) }
+
+ before do
+ build_team(merge_request.target_project)
+ end
+
+ describe :new_merge_request do
+ it do
+ should_email(merge_request.assignee_id)
+ should_email(@u_watcher.id)
+ should_not_email(@u_participating.id)
+ should_not_email(@u_disabled.id)
+ notification.new_merge_request(merge_request, @u_disabled)
+ end
+
+ def should_email(user_id)
+ Notify.should_receive(:new_merge_request_email).with(user_id, merge_request.id)
+ end
+
+ def should_not_email(user_id)
+ Notify.should_not_receive(:new_merge_request_email).with(user_id, merge_request.id)
+ end
+ end
+
+ describe :reassigned_merge_request do
+ it do
+ should_email(merge_request.assignee_id)
+ should_email(@u_watcher.id)
+ should_not_email(@u_participating.id)
+ should_not_email(@u_disabled.id)
+ notification.reassigned_merge_request(merge_request, merge_request.author)
+ end
+
+ def should_email(user_id)
+ Notify.should_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_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)
+ end
+ end
+
+ describe :closed_merge_request do
+ it do
+ should_email(merge_request.assignee_id)
+ should_email(@u_watcher.id)
+ should_not_email(@u_participating.id)
+ should_not_email(@u_disabled.id)
+ notification.close_mr(merge_request, @u_disabled)
+ end
+
+ def should_email(user_id)
+ Notify.should_receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
+ end
+
+ def should_not_email(user_id)
+ Notify.should_not_receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
+ end
+ end
+
+ describe :merged_merge_request do
+ it do
+ should_email(merge_request.assignee_id)
+ should_email(@u_watcher.id)
+ should_not_email(@u_participating.id)
+ should_not_email(@u_disabled.id)
+ notification.merge_mr(merge_request)
+ end
+
+ def should_email(user_id)
+ Notify.should_receive(:merged_merge_request_email).with(user_id, merge_request.id)
+ end
+
+ def should_not_email(user_id)
+ Notify.should_not_receive(:merged_merge_request_email).with(user_id, merge_request.id)
+ end
+ end
+ end
+
+ def build_team(project)
+ @u_watcher = create(:user, notification_level: Notification::N_WATCH)
+ @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING)
+ @u_disabled = create(:user, notification_level: Notification::N_DISABLED)
+ @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_PARTICIPATING)
+ @u_committer = create(:user, username: 'committer')
+
+ project.team << [@u_watcher, :master]
+ project.team << [@u_participating, :master]
+ project.team << [@u_disabled, :master]
+ project.team << [@u_mentioned, :master]
+ project.team << [@u_committer, :master]
+ end
+end
diff --git a/spec/services/project_transfer_service_spec.rb b/spec/services/project_transfer_service_spec.rb
new file mode 100644
index 00000000000..109b429967e
--- /dev/null
+++ b/spec/services/project_transfer_service_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe ProjectTransferService do
+ before(:each) { enable_observers }
+ after(:each) {disable_observers}
+
+ context 'namespace -> namespace' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ @result = service.transfer(project, group)
+ end
+
+ it { @result.should be_true }
+ it { project.namespace.should == group }
+ end
+
+ context 'namespace -> no namespace' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ it { lambda{service.transfer(project, nil)}.should raise_error(ActiveRecord::RecordInvalid) }
+ end
+
+ def service
+ service = ProjectTransferService.new
+ service.gitlab_shell.stub(mv_repository: true)
+ service
+ end
+end
+
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
new file mode 100644
index 00000000000..7f1590f559e
--- /dev/null
+++ b/spec/services/system_hooks_service_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe SystemHooksService do
+ let (:user) { create :user }
+ let (:project) { create :project }
+ let (:users_project) { create :users_project }
+
+ context 'it should build event data' do
+ it 'should build event data for user' do
+ SystemHooksService.build_event_data(user, :create).should include(:event_name, :name, :created_at, :email)
+ end
+
+ it 'should build event data for project' do
+ SystemHooksService.build_event_data(project, :create).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email)
+ end
+
+ it 'should build event data for users project' do
+ SystemHooksService.build_event_data(users_project, :create).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access)
+ end
+ end
+
+ context 'it should build event names' do
+ it 'should build event names for user' do
+ SystemHooksService.build_event_name(user, :create).should eq "user_create"
+
+ SystemHooksService.build_event_name(user, :destroy).should eq "user_destroy"
+ end
+
+ it 'should build event names for project' do
+ SystemHooksService.build_event_name(project, :create).should eq "project_create"
+
+ SystemHooksService.build_event_name(project, :destroy).should eq "project_destroy"
+ end
+
+ it 'should build event names for users project' do
+ SystemHooksService.build_event_name(users_project, :create).should eq "user_add_to_team"
+
+ SystemHooksService.build_event_name(users_project, :destroy).should eq "user_remove_from_team"
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 77497991f99..dd008ed02ad 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,42 +1,63 @@
-require 'simplecov' unless ENV['CI']
-
-
-# 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'
-
-# 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}
-
-require 'capybara/poltergeist'
-Capybara.javascript_driver = :poltergeist
-
-WebMock.disable_net_connect!(allow_localhost: true)
-
-RSpec.configure do |config|
- config.mock_with :rspec
-
- config.include LoginHelpers, type: :request
- config.include FactoryGirl::Syntax::Methods
- config.include Devise::TestHelpers, type: :controller
-
- # 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 do
- # Use tmp dir for FS manipulations
- temp_repos_path = Rails.root.join('tmp', 'test-git-base-path')
- Gitlab.config.gitlab_shell.stub(repos_path: temp_repos_path)
- FileUtils.rm_rf temp_repos_path
- FileUtils.mkdir_p temp_repos_path
+require 'rubygems'
+require 'spork'
+
+Spork.prefork do
+ 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
end
+
+Spork.each_run do
+ # This code will be run each time you run your specs.
+
+end
diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb
index c4514bf38be..ec9a326a1ea 100644
--- a/spec/support/api_helpers.rb
+++ b/spec/support/api_helpers.rb
@@ -18,7 +18,7 @@ module ApiHelpers
#
# Returns the relative path to the requested API resource
def api(path, user = nil)
- "/api/#{Gitlab::API.version}#{path}" +
+ "/api/#{API::API.version}#{path}" +
# Normalize query string
(path.index('?') ? '' : '?') +
diff --git a/spec/support/big_commits.rb b/spec/support/big_commits.rb
new file mode 100644
index 00000000000..69daa709dd9
--- /dev/null
+++ b/spec/support/big_commits.rb
@@ -0,0 +1,8 @@
+module BigCommits
+ HUGE_COMMIT_ID = "7f92534f767fa20357a11c63f973ae3b79cc5b85"
+ HUGE_COMMIT_MESSAGE = "pybments.rb version up. gitignore improved"
+
+ BIG_COMMIT_ID = "d62200cad430565bd9f80befaf329297120330b5"
+ BIG_COMMIT_MESSAGE = "clean-up code"
+end
+
diff --git a/spec/support/chosen_helper.rb b/spec/support/chosen_helper.rb
new file mode 100644
index 00000000000..42c9342c77a
--- /dev/null
+++ b/spec/support/chosen_helper.rb
@@ -0,0 +1,21 @@
+# Chosen programmatic helper
+# It allows you to select value from chosen select
+#
+# Params
+# value - real value of selected item
+# opts - options containing css selector
+#
+# Usage:
+#
+# chosen(2, from: '#user_ids')
+#
+
+module ChosenHelper
+ def chosen(value, options={})
+ raise "Must pass a hash containing 'from'" if not options.is_a?(Hash) or not options.has_key?(:from)
+
+ selector = options[:from]
+
+ page.execute_script("$('#{selector}').val('#{value}').trigger('chosen:updated');")
+ end
+end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index f1e072aa15f..8c9c74f14bd 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -9,10 +9,14 @@ RSpec.configure do |config|
DatabaseCleaner.strategy = :transaction
end
- DatabaseCleaner.start
+ unless example.metadata[:no_db]
+ DatabaseCleaner.start
+ end
end
config.after do
- DatabaseCleaner.clean
+ unless example.metadata[:no_db]
+ DatabaseCleaner.clean
+ end
end
end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index 4579c971d47..025534a900d 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -3,7 +3,10 @@ module LoginHelpers
#
# role - User role (e.g., :admin, :user)
def login_as(role)
- @user = create(role)
+ ActiveRecord::Base.observers.enable(:user_observer) do
+ @user = create(role)
+ end
+
login_with(@user)
end
@@ -12,9 +15,10 @@ module LoginHelpers
# user - User instance to login with
def login_with(user)
visit new_user_session_path
- fill_in "user_email", with: user.email
+ fill_in "user_login", with: user.email
fill_in "user_password", with: "123456"
click_button "Sign in"
+ Thread.current[:current_user] = user
end
def logout
diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb
index 29d16ecbac7..15fb47004e9 100644
--- a/spec/support/matchers.rb
+++ b/spec/support/matchers.rb
@@ -3,7 +3,7 @@ RSpec::Matchers.define :be_valid_commit do
actual != nil
actual.id == ValidCommit::ID
actual.message == ValidCommit::MESSAGE
- actual.author.name == ValidCommit::AUTHOR_FULL_NAME
+ actual.author_name == ValidCommit::AUTHOR_FULL_NAME
end
end
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
new file mode 100644
index 00000000000..a7f189777c8
--- /dev/null
+++ b/spec/support/mentionable_shared_examples.rb
@@ -0,0 +1,94 @@
+# Specifications for behavior common to all Mentionable implementations.
+# Requires a shared context containing:
+# - let(:subject) { "the mentionable implementation" }
+# - let(:backref_text) { "the way that +subject+ should refer to itself in backreferences " }
+# - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } }
+
+def common_mentionable_setup
+ # Avoid name collisions with let(:project) or let(:author) in the surrounding scope.
+ let(:mproject) { create :project }
+ let(:mauthor) { subject.author }
+
+ let(:mentioned_issue) { create :issue, project: mproject }
+ let(:other_issue) { create :issue, project: mproject }
+ let(:mentioned_mr) { create :merge_request, target_project: mproject, source_branch: 'different' }
+ let(:mentioned_commit) { mock('commit', sha: '1234567890abcdef').as_null_object }
+
+ # Override to add known commits to the repository stub.
+ let(:extra_commits) { [] }
+
+ # A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference
+ # to this string and place it in their +mentionable_text+.
+ let(:ref_string) do
+ "mentions ##{mentioned_issue.iid} twice ##{mentioned_issue.iid}, !#{mentioned_mr.iid}, " +
+ "#{mentioned_commit.sha[0..5]} and itself as #{backref_text}"
+ end
+
+ before do
+ # Wire the project's repository to return the mentioned commit, and +nil+ for any
+ # unrecognized commits.
+ commitmap = { '123456' => mentioned_commit }
+ extra_commits.each { |c| commitmap[c.sha[0..5]] = c }
+
+ repo = mock('repository')
+ repo.stub(:commit) { |sha| commitmap[sha] }
+ mproject.stub(repository: repo)
+
+ set_mentionable_text.call(ref_string)
+ end
+end
+
+shared_examples 'a mentionable' do
+ common_mentionable_setup
+
+ it 'generates a descriptive back-reference' do
+ subject.gfm_reference.should == backref_text
+ end
+
+ it "extracts references from its reference property" do
+ # De-duplicate and omit itself
+ refs = subject.references(mproject)
+
+ refs.should have(3).items
+ refs.should include(mentioned_issue)
+ refs.should include(mentioned_mr)
+ refs.should include(mentioned_commit)
+ end
+
+ it 'creates cross-reference notes' do
+ [mentioned_issue, mentioned_mr, mentioned_commit].each do |referenced|
+ Note.should_receive(:create_cross_reference_note).with(referenced, subject.local_reference, mauthor, mproject)
+ end
+
+ subject.create_cross_references!(mproject, mauthor)
+ end
+
+ it 'detects existing cross-references' do
+ Note.create_cross_reference_note(mentioned_issue, subject.local_reference, mauthor, mproject)
+
+ subject.has_mentioned?(mentioned_issue).should be_true
+ subject.has_mentioned?(mentioned_mr).should be_false
+ end
+end
+
+shared_examples 'an editable mentionable' do
+ common_mentionable_setup
+
+ it_behaves_like 'a mentionable'
+
+ it 'creates new cross-reference notes when the mentionable text is edited' do
+ new_text = "this text still mentions ##{mentioned_issue.iid} and #{mentioned_commit.sha[0..5]}, " +
+ "but now it mentions ##{other_issue.iid}, too."
+
+ [mentioned_issue, mentioned_commit].each do |oldref|
+ Note.should_not_receive(:create_cross_reference_note).with(oldref, subject.local_reference,
+ mauthor, mproject)
+ end
+
+ Note.should_receive(:create_cross_reference_note).with(other_issue, subject.local_reference, mauthor, mproject)
+
+ subject.save
+ set_mentionable_text.call(new_text)
+ subject.notice_added_references(mproject, mauthor)
+ end
+end
diff --git a/spec/support/select2_helper.rb b/spec/support/select2_helper.rb
new file mode 100644
index 00000000000..32c1ded2e9d
--- /dev/null
+++ b/spec/support/select2_helper.rb
@@ -0,0 +1,25 @@
+# Select2 ajax programmatic helper
+# It allows you to select value from select2
+#
+# Params
+# value - real value of selected item
+# opts - options containing css selector
+#
+# Usage:
+#
+# select2(2, from: '#user_ids')
+#
+
+module Select2Helper
+ def select2(value, options={})
+ raise "Must pass a hash containing 'from'" if not options.is_a?(Hash) or not options.has_key?(:from)
+
+ selector = options[:from]
+
+ if options[:multiple]
+ page.execute_script("$('#{selector}').select2('val', ['#{value}']);")
+ else
+ page.execute_script("$('#{selector}').select2('val', '#{value}');")
+ end
+ end
+end
diff --git a/spec/support/stubbed_repository.rb b/spec/support/stubbed_repository.rb
deleted file mode 100644
index 434cab6516e..00000000000
--- a/spec/support/stubbed_repository.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require "repository"
-require "project"
-require "shell"
-
-# Stubs out all Git repository access done by models so that specs can run
-# against fake repositories without Grit complaining that they don't exist.
-class Project
- def repository
- if path == "empty" || !path
- nil
- else
- GitLabTestRepo.new(path_with_namespace)
- end
- end
-
- def satellite
- FakeSatellite.new
- end
-
- class FakeSatellite
- def exists?
- true
- end
-
- def destroy
- true
- end
-
- def create
- true
- end
- end
-end
-
-class GitLabTestRepo < Repository
- def repo
- @repo ||= Grit::Repo.new(Rails.root.join('tmp', 'repositories', 'gitlabhq'))
- end
-end
-
-module Gitlab
- class Shell
- def add_repository name
- true
- end
-
- def remove_repository name
- true
- end
-
- def add_key id, key
- true
- end
-
- def remove_key id, key
- true
- end
- end
-end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
new file mode 100644
index 00000000000..203e661f514
--- /dev/null
+++ b/spec/support/test_env.rb
@@ -0,0 +1,173 @@
+require 'rspec/mocks'
+
+module TestEnv
+ extend self
+
+ # Test environment
+ #
+ # all repositories and namespaces stored at
+ # RAILS_APP/tmp/test-git-base-path
+ #
+ # Next shell methods are stubbed and return true
+ # - mv_repository
+ # - remove_repository
+ # - add_key
+ # - remove_key
+ #
+ def init(opts = {})
+ RSpec::Mocks::setup(self)
+
+ # Disable observers to improve test speed
+ #
+ # You can enable it in whole test case where needed by next string:
+ #
+ # before(:each) { enable_observers }
+ #
+ disable_observers if opts[:observers] == false
+
+ # Disable mailer for spinach tests
+ 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
+
+ def enable_observers
+ ActiveRecord::Base.observers.enable(:all)
+ end
+
+ def disable_observers
+ ActiveRecord::Base.observers.disable(:all)
+ end
+
+ def disable_mailer
+ NotificationService.any_instance.stub(mailer: double.as_null_object)
+ end
+ def enable_mailer
+ NotificationService.any_instance.unstub(:mailer)
+ end
+
+ def setup_stubs()
+ # Use tmp dir for FS manipulations
+ repos_path = testing_path()
+ GollumWiki.any_instance.stub(:init_repo) do |path|
+ create_temp_repo(File.join(repos_path, "#{path}.git"))
+ end
+
+ Gitlab.config.gitlab_shell.stub(repos_path: repos_path)
+
+ Gitlab.config.satellites.stub(path: satellite_path)
+
+ Gitlab::Git::Repository.stub(repos_path: repos_path)
+
+ Gitlab::Shell.any_instance.stub(
+ add_repository: true,
+ mv_repository: true,
+ remove_repository: true,
+ update_repository_head: true,
+ add_key: true,
+ remove_key: true
+ )
+
+ Gitlab::Satellite::Satellite.any_instance.stub(
+ exists?: true,
+ destroy: true,
+ create: true,
+ lock_files_dir: repos_path
+ )
+
+ MergeRequest.any_instance.stub(
+ check_if_can_be_merged: true
+ )
+ Repository.any_instance.stub(
+ size: 12.45
+ )
+ end
+
+ def clear_repo_dir(namespace, name)
+ setup_stubs
+ # Clean any .wiki.git that may have been created
+ FileUtils.rm_rf File.join(testing_path(), "#{name}.wiki.git")
+ end
+
+ # Create a repo and it's satellite
+ def create_repo(namespace, name)
+ setup_stubs
+ repo = repo(namespace, name)
+
+ # Symlink tmp/repositories/gitlabhq to tmp/test-git-base-path/gitlabhq
+ system("ln -s -f #{seed_repo_path()} #{repo}")
+ create_satellite(repo, namespace, name)
+ end
+
+ private
+
+ def testing_path
+ Rails.root.join('tmp', 'test-git-base-path')
+ end
+
+ def seed_repo_path
+ Rails.root.join('tmp', 'repositories', 'gitlabhq')
+ end
+
+ def seed_satellite_path
+ Rails.root.join('tmp', 'satellite', 'gitlabhq')
+ end
+
+ def satellite_path
+ "#{testing_path()}/satellite"
+ end
+
+ def repo(namespace, name)
+ unless (namespace.nil? || namespace.path.nil? || namespace.path.strip.empty?)
+ repo = File.join(testing_path(), "#{namespace.path}/#{name}.git")
+ else
+ repo = File.join(testing_path(), "#{name}.git")
+ end
+ end
+
+ def satellite(namespace, name)
+ unless (namespace.nil? || namespace.path.nil? || namespace.path.strip.empty?)
+ satellite_repo = File.join(satellite_path, namespace.path, name)
+ else
+ satellite_repo = File.join(satellite_path, name)
+ end
+ end
+
+ def setup_test_repos(opts ={})
+ create_repo(nil, 'gitlabhq') #unless opts[:repo].nil? || !opts[:repo].include?('')
+ create_repo(nil, 'source_gitlabhq') #unless opts[:repo].nil? || !opts[:repo].include?('source_')
+ create_repo(nil, 'target_gitlabhq') #unless opts[:repo].nil? || !opts[:repo].include?('target_')
+ end
+
+ 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
+
+ # Recreate tmp/test-git-base-path
+ FileUtils.mkdir_p Gitlab.config.gitlab_shell.repos_path
+
+ # Since much more is happening in satellites
+ FileUtils.mkdir_p Gitlab.config.satellites.path
+ end
+
+ # Create a testing satellite, and clone the source repo into it
+ def create_satellite(source_repo, namespace, satellite_name)
+ satellite_repo = satellite(namespace, satellite_name)
+ # 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}")
+ end
+
+ def create_temp_repo(path)
+ FileUtils.mkdir_p path
+ command = "git init --quiet --bare #{path};"
+ system(command)
+ end
+end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index babbf2916f8..cba243226db 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -22,21 +22,21 @@ describe 'gitlab:app namespace rake task' do
context 'gitlab version' do
before do
- Dir.stub :glob => []
+ Dir.stub glob: []
Dir.stub :chdir
- File.stub :exists? => true
- Kernel.stub :system => true
+ File.stub exists?: true
+ Kernel.stub system: true
end
let(:gitlab_version) { %x{git rev-parse HEAD}.gsub(/\n/,"") }
- it 'should fail on mismach' do
- YAML.stub :load_file => {:gitlab_version => gitlab_version.reverse}
+ it 'should fail on mismatch' do
+ YAML.stub load_file: {gitlab_version: gitlab_version.reverse}
expect { run_rake_task }.to raise_error SystemExit
end
it 'should invoke restoration on mach' do
- YAML.stub :load_file => {:gitlab_version => gitlab_version}
+ YAML.stub load_file: {gitlab_version: gitlab_version}
Rake::Task["gitlab:backup:db:restore"].should_receive :invoke
Rake::Task["gitlab:backup:repo:restore"].should_receive :invoke
expect { run_rake_task }.to_not raise_error SystemExit
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index d38cd59efa7..46e86dbe00a 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -9,7 +9,7 @@ describe PostReceive do
end
context "web hook" do
- let(:project) { create(:project) }
+ let(:project) { create(:project_with_code) }
let(:key) { create(:key, user: project.owner) }
let(:key_id) { key.shell_id }
@@ -21,7 +21,6 @@ describe PostReceive do
it "does not run if the author is not in the project" do
Key.stub(find_by_id: nil)
- project.should_not_receive(:observe_push)
project.should_not_receive(:execute_hooks)
PostReceive.new.perform(pwd(project), 'sha-old', 'sha-new', 'refs/heads/master', key_id).should be_false
@@ -32,7 +31,6 @@ describe PostReceive do
project.should_receive(:execute_hooks)
project.should_receive(:execute_services)
project.should_receive(:update_merge_requests)
- project.should_receive(:observe_push)
PostReceive.new.perform(pwd(project), 'sha-old', 'sha-new', 'refs/heads/master', key_id)
end
diff --git a/vendor/assets/javascripts/ace-src-noconflict/ace.js b/vendor/assets/javascripts/ace-src-noconflict/ace.js
index 11b4bbb8da3..8bd2d9a6051 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/ace.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/ace.js
@@ -4800,7 +4800,7 @@ var SearchHighlight = require("./search_highlight").SearchHighlight;
/**
* new EditSession(text, mode)
* - text (Document | String): If `text` is a `Document`, it associates the `EditSession` with it. Otherwise, a new `Document` is created, with the initial text
- * - mode (TextMode): The inital language mode to use for the document
+ * - mode (TextMode): The initial language mode to use for the document
*
* Sets up a new `EditSession` and associates it with the given `Document` and `TextMode`.
*
@@ -10068,7 +10068,7 @@ ace.define('ace/token_iterator', ['require', 'exports', 'module' ], function(req
* - initialRow (Number): The row to start the tokenizing at
* - initialColumn (Number): The column to start the tokenizing at
*
- * Creates a new token iterator object. The inital token index is set to the provided row and column coordinates.
+ * Creates a new token iterator object. The initial token index is set to the provided row and column coordinates.
*
**/
var TokenIterator = function(session, initialRow, initialColumn) {
@@ -11946,7 +11946,7 @@ var VirtualRenderer = function(container, theme) {
this.$horizScroll = horizScroll;
if (horizScrollChanged) {
this.scroller.style.overflowX = horizScroll ? "scroll" : "hidden";
- // when we hide scrollbar scroll event isn't emited
+ // when we hide scrollbar scroll event isn't emitted
// leaving session with wrong scrollLeft value
if (!horizScroll)
this.session.setScrollLeft(0);
@@ -13029,7 +13029,7 @@ var Text = function(parentEl) {
var html = [];
// Get the tokens per line as there might be some lines in between
- // beeing folded.
+ // being folded.
this.$renderLine(html, row, false, row == foldStart ? foldLine : false);
// don't use setInnerHtml since we are working with an empty DIV
@@ -14529,7 +14529,7 @@ var Editor = require("./editor").Editor;
* - dir (Number): The direction of lines to select: -1 for up, 1 for down
* - skip (Boolean): If `true`, removes the active selection range
*
- * Finds the next occurence of text in an active selection and adds it to the selections.
+ * Finds the next occurrence of text in an active selection and adds it to the selections.
**/
this.selectMore = function(dir, skip) {
var session = this.session;
@@ -15878,4 +15878,4 @@ ace.define("text!ace/theme/textmate.css", [], ".ace-tm .ace_editor {\n" +
ace[key] = a[key];
});
})();
- \ No newline at end of file
+
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-coldfusion.js b/vendor/assets/javascripts/ace-src-noconflict/mode-coldfusion.js
index 1b41d001a25..aea4214560e 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-coldfusion.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-coldfusion.js
@@ -1170,7 +1170,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-groovy.js b/vendor/assets/javascripts/ace-src-noconflict/mode-groovy.js
index ad7bdf58855..80d153d8fbd 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-groovy.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-groovy.js
@@ -337,7 +337,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-html.js b/vendor/assets/javascripts/ace-src-noconflict/mode-html.js
index 0ea36845d75..5ec11f141c0 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-html.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-html.js
@@ -389,7 +389,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-jade.js b/vendor/assets/javascripts/ace-src-noconflict/mode-jade.js
index c01a359835a..32aafd17c9e 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-jade.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-jade.js
@@ -659,7 +659,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-java.js b/vendor/assets/javascripts/ace-src-noconflict/mode-java.js
index 23f9e60396b..c05bf0f9e75 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-java.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-java.js
@@ -338,7 +338,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-javascript.js b/vendor/assets/javascripts/ace-src-noconflict/mode-javascript.js
index d266bffb19a..90cf57eb5f4 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-javascript.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-javascript.js
@@ -342,7 +342,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-jsp.js b/vendor/assets/javascripts/ace-src-noconflict/mode-jsp.js
index c96d25e936e..e4f4bcffed3 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-jsp.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-jsp.js
@@ -597,7 +597,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-liquid.js b/vendor/assets/javascripts/ace-src-noconflict/mode-liquid.js
index 2623396ca0a..3886e739ba4 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-liquid.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-liquid.js
@@ -641,7 +641,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-luahtml.js b/vendor/assets/javascripts/ace-src-noconflict/mode-luahtml.js
index 89bc2ec783f..d8a1b653f3d 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-luahtml.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-luahtml.js
@@ -466,7 +466,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
@@ -2412,4 +2412,4 @@ oop.inherits(LuaHtmlHighlightRules, HtmlHighlightRules);
exports.LuaHtmlHighlightRules = LuaHtmlHighlightRules;
-}); \ No newline at end of file
+});
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-luapage.js b/vendor/assets/javascripts/ace-src-noconflict/mode-luapage.js
index 8f03866ddaf..9be136de978 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-luapage.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-luapage.js
@@ -382,7 +382,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
@@ -2477,4 +2477,4 @@ oop.inherits(LuaPageHighlightRules, HtmlHighlightRules);
exports.LuaPageHighlightRules = LuaPageHighlightRules;
-}); \ No newline at end of file
+});
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-markdown.js b/vendor/assets/javascripts/ace-src-noconflict/mode-markdown.js
index 179376aa403..e1c269eefc2 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-markdown.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-markdown.js
@@ -384,7 +384,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-php.js b/vendor/assets/javascripts/ace-src-noconflict/mode-php.js
index d3deefc6618..40710ceef20 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-php.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-php.js
@@ -1685,7 +1685,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-scala.js b/vendor/assets/javascripts/ace-src-noconflict/mode-scala.js
index ee8ebb8fc0c..87b8e12b8e0 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-scala.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-scala.js
@@ -338,7 +338,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/mode-svg.js b/vendor/assets/javascripts/ace-src-noconflict/mode-svg.js
index 29c2b17bdd3..96b965ba112 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/mode-svg.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/mode-svg.js
@@ -1177,7 +1177,7 @@ var JavaScriptHighlightRules = function() {
}
],
// regular expressions are only allowed after certain tokens. This
- // makes sure we don't mix up regexps with the divison operator
+ // makes sure we don't mix up regexps with the division operator
"regex_allowed": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
diff --git a/vendor/assets/javascripts/ace-src-noconflict/worker-javascript.js b/vendor/assets/javascripts/ace-src-noconflict/worker-javascript.js
index 923bac6f661..671d7bec547 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/worker-javascript.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/worker-javascript.js
@@ -2912,7 +2912,7 @@ var JSHINT = (function () {
immed : true, // if immediate invocations must be wrapped in parens
iterator : true, // if the `__iterator__` property should be allowed
jquery : true, // if jQuery globals should be predefined
- lastsemic : true, // if semicolons may be ommitted for the trailing
+ lastsemic : true, // if semicolons may be omitted for the trailing
// statements inside of a one-line blocks.
latedef : true, // if the use before definition should not be tolerated
laxbreak : true, // if line breaks should not be checked
@@ -3674,7 +3674,7 @@ var JSHINT = (function () {
line += 1;
// If smarttabs option is used check for spaces followed by tabs only.
- // Otherwise check for any occurence of mixed tabs and spaces.
+ // Otherwise check for any occurrence of mixed tabs and spaces.
if (option.smarttabs)
at = s.search(/ \t/);
else
diff --git a/vendor/assets/javascripts/ace-src-noconflict/worker-xquery.js b/vendor/assets/javascripts/ace-src-noconflict/worker-xquery.js
index 494066fd73f..1d8c512ee77 100644
--- a/vendor/assets/javascripts/ace-src-noconflict/worker-xquery.js
+++ b/vendor/assets/javascripts/ace-src-noconflict/worker-xquery.js
@@ -7609,7 +7609,7 @@ org.antlr.runtime.BaseRecognizer.prototype = {
*
* Until then I'll leave this unimplemented. If there is enough clamor
* it would be possible to keep track of the invocation stack using an
- * auxillary array, but that will definitely be a performance hit.
+ * auxiliary array, but that will definitely be a performance hit.
*/
getRuleInvocationStack: function(e, recognizerClassName)
{
diff --git a/vendor/assets/javascripts/branch-graph.js b/vendor/assets/javascripts/branch-graph.js
deleted file mode 100644
index fb22953acd2..00000000000
--- a/vendor/assets/javascripts/branch-graph.js
+++ /dev/null
@@ -1,385 +0,0 @@
-!function(){
-
- var BranchGraph = function(element, options){
- this.element = element;
- this.options = options;
-
- this.preparedCommits = {};
- this.mtime = 0;
- this.mspace = 0;
- this.parents = {};
- this.colors = ["#000"];
-
- this.load();
- };
-
- BranchGraph.prototype.load = function(){
- $.ajax({
- url: this.options.url,
- method: 'get',
- dataType: 'json',
- success: $.proxy(function(data){
- $('.loading', this.element).hide();
- this.prepareData(data.days, data.commits);
- this.buildGraph();
- }, this)
- });
- };
-
- BranchGraph.prototype.prepareData = function(days, commits){
- this.days = days;
- this.dayCount = days.length;
- this.commits = commits;
- this.commitCount = commits.length;
-
- this.collectParents();
-
- this.mtime += 4;
- this.mspace += 10;
- for (var i = 0; i < this.commitCount; i++) {
- if (this.commits[i].id in this.parents) {
- this.commits[i].isParent = true;
- }
- this.preparedCommits[this.commits[i].id] = this.commits[i];
- }
- this.collectColors();
- };
-
- BranchGraph.prototype.collectParents = function(){
- for (var i = 0; i < this.commitCount; i++) {
- for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) {
- this.parents[this.commits[i].parents[j][0]] = true;
- }
- this.mtime = Math.max(this.mtime, this.commits[i].time);
- this.mspace = Math.max(this.mspace, this.commits[i].space);
- }
- };
-
- BranchGraph.prototype.collectColors = function(){
- for (var k = 0; k < this.mspace; k++) {
- this.colors.push(Raphael.getColor(.8));
- // Skipping a few colors in the spectrum to get more contrast between colors
- Raphael.getColor();Raphael.getColor();
- }
- };
-
- BranchGraph.prototype.buildGraph = function(){
- var graphWidth = $(this.element).width()
- , ch = this.mspace * 20 + 100
- , cw = Math.max(graphWidth, this.mtime * 20 + 260)
- , r = Raphael(this.element.get(0), cw, ch)
- , top = r.set()
- , cuday = 0
- , cumonth = ""
- , offsetX = 20
- , offsetY = 60
- , barWidth = Math.max(graphWidth, this.dayCount * 20 + 320)
- , scrollLeft = cw;
-
- this.raphael = r;
-
- r.rect(0, 0, barWidth, 20).attr({fill: "#222"});
- r.rect(0, 20, barWidth, 20).attr({fill: "#444"});
-
- for (mm = 0; mm < this.dayCount; mm++) {
- if(this.days[mm] != null){
- if(cuday != this.days[mm][0]){
- // Dates
- r.text(offsetX + mm * 20, 31, this.days[mm][0]).attr({
- font: "12px Monaco, monospace",
- fill: "#DDD"
- });
- cuday = this.days[mm][0];
- }
- if(cumonth != this.days[mm][1]){
- // Months
- r.text(offsetX + mm * 20, 11, this.days[mm][1]).attr({
- font: "12px Monaco, monospace",
- fill: "#EEE"
- });
- cumonth = this.days[mm][1];
- }
- }
- }
-
- for (i = 0; i < this.commitCount; i++) {
- var x = offsetX + 20 * this.commits[i].time
- , y = offsetY + 10 * this.commits[i].space
- , c
- , ps;
-
- // Draw dot
- r.circle(x, y, 3).attr({
- fill: this.colors[this.commits[i].space],
- stroke: "none"
- });
-
- // Draw lines
- for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) {
- c = this.preparedCommits[this.commits[i].parents[j][0]];
- ps = this.commits[i].parent_spaces[j];
- if (c) {
- var cx = offsetX + 20 * c.time
- , cy = offsetY + 10 * c.space
- , psy = offsetY + 10 * ps;
- if (c.space == this.commits[i].space && c.space == ps) {
- r.path([
- "M", x, y,
- "L", cx, cy
- ]).attr({
- stroke: this.colors[c.space],
- "stroke-width": 2
- });
-
- } else if (c.space < this.commits[i].space) {
- r.path([
- "M", x - 5, y,
- "l-5-2,0,4,5,-2",
- "L", x - 10, y,
- "L", x - 15, psy,
- "L", cx + 5, psy,
- "L", cx, cy])
- .attr({
- stroke: this.colors[this.commits[i].space],
- "stroke-width": 2
- });
- } else {
- r.path([
- "M", x - 3, y + 6,
- "l-4,3,4,2,0,-5",
- "L", x - 5, y + 10,
- "L", x - 10, psy,
- "L", cx + 5, psy,
- "L", cx, cy])
- .attr({
- stroke: this.colors[c.space],
- "stroke-width": 2
- });
- }
- }
- }
-
- if (this.commits[i].refs) {
- this.appendLabel(x, y, this.commits[i].refs);
- }
-
- // mark commit and displayed in the center
- if (this.commits[i].id == this.options.commit_id) {
- r.path([
- 'M', x, y - 5,
- 'L', x + 4, y - 15,
- 'L', x - 4, y - 15,
- 'Z'
- ]).attr({
- "fill": "#000",
- "fill-opacity": .7,
- "stroke": "none"
- });
- scrollLeft = x - graphWidth / 2;
- }
-
- this.appendAnchor(top, this.commits[i], x, y);
- }
- top.toFront();
- this.element.scrollLeft(scrollLeft);
- this.bindEvents();
- };
-
- BranchGraph.prototype.bindEvents = function(){
- var drag = {}
- , element = this.element;
-
- var dragger = function(event){
- element.scrollLeft(drag.sl - (event.clientX - drag.x));
- element.scrollTop(drag.st - (event.clientY - drag.y));
- };
-
- element.on({
- mousedown: function (event) {
- drag = {
- x: event.clientX,
- y: event.clientY,
- st: element.scrollTop(),
- sl: element.scrollLeft()
- };
- $(window).on('mousemove', dragger);
- }
- });
- $(window).on({
- mouseup: function(){
- //bars.animate({opacity: 0}, 300);
- $(window).off('mousemove', dragger);
- },
- keydown: function(event){
- if(event.keyCode == 37){
- // left
- element.scrollLeft( element.scrollLeft() - 50);
- }
- if(event.keyCode == 38){
- // top
- element.scrollTop( element.scrollTop() - 50);
- }
- if(event.keyCode == 39){
- // right
- element.scrollLeft( element.scrollLeft() + 50);
- }
- if(event.keyCode == 40){
- // bottom
- element.scrollTop( element.scrollTop() + 50);
- }
- }
- });
- };
-
- BranchGraph.prototype.appendLabel = function(x, y, refs){
- var r = this.raphael
- , shortrefs = refs
- , text, textbox, rect;
-
- if (shortrefs.length > 17){
- // Truncate if longer than 15 chars
- shortrefs = shortrefs.substr(0,15) + "…";
- }
-
- text = r.text(x+5, y+8 + 10, shortrefs).attr({
- font: "10px Monaco, monospace",
- fill: "#FFF",
- title: refs
- });
-
- textbox = text.getBBox();
- text.transform([
- 't', textbox.height/-4, textbox.width/2 + 5,
- 'r90'
- ]);
-
- // Create rectangle based on the size of the textbox
- rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr({
- "fill": "#000",
- "fill-opacity": .7,
- "stroke": "none"
- });
-
- triangle = r.path([
- 'M', x, y + 5,
- 'L', x + 4, y + 15,
- 'L', x - 4, y + 15,
- 'Z'
- ]).attr({
- "fill": "#000",
- "fill-opacity": .7,
- "stroke": "none"
- });
-
- // Rotate and reposition rectangle over text
- rect.transform([
- 'r', 90, x, y,
- 't', 15, -9
- ]);
-
- // Set text to front
- text.toFront();
- };
-
- BranchGraph.prototype.appendAnchor = function(top, commit, x, y) {
- var r = this.raphael
- , options = this.options
- , anchor;
- anchor = r.circle(x, y, 10).attr({
- fill: "#000",
- opacity: 0,
- cursor: "pointer"
- })
- .click(function(){
- window.open(options.commit_url.replace('%s', commit.id), '_blank');
- })
- .hover(function(){
- this.tooltip = r.commitTooltip(x, y + 5, commit);
- top.push(this.tooltip.insertBefore(this));
- }, function(){
- this.tooltip && this.tooltip.remove() && delete this.tooltip;
- });
- top.push(anchor);
- };
-
- this.BranchGraph = BranchGraph;
-
-}(this);
-Raphael.fn.commitTooltip = function(x, y, commit){
- var nameText, idText, messageText
- , boxWidth = 300
- , boxHeight = 200;
-
- nameText = this.text(x, y + 10, commit.author.name);
- idText = this.text(x, y + 35, commit.id);
- messageText = this.text(x, y + 50, commit.message);
-
- textSet = this.set(nameText, idText, messageText).attr({
- "text-anchor": "start",
- "font": "12px Monaco, monospace"
- });
-
- nameText.attr({
- "font": "14px Arial",
- "font-weight": "bold"
- });
-
- idText.attr({
- "fill": "#AAA"
- });
-
- textWrap(messageText, boxWidth - 50);
-
- var rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({
- "fill": "#FFF",
- "stroke": "#000",
- "stroke-linecap": "round",
- "stroke-width": 2
- });
- var tooltip = this.set(rect, textSet);
-
- rect.attr({
- "height" : tooltip.getBBox().height + 10,
- "width" : tooltip.getBBox().width + 10
- });
-
- tooltip.transform([
- 't', 20, 20
- ]);
-
- return tooltip;
-};
-
-function textWrap(t, width) {
- var content = t.attr("text");
- var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
- t.attr({
- "text" : abc
- });
- var letterWidth = t.getBBox().width / abc.length;
-
- t.attr({
- "text" : content
- });
-
- var words = content.split(" ");
- var x = 0, s = [];
- for ( var i = 0; i < words.length; i++) {
-
- var l = words[i].length;
- if (x + (l * letterWidth) > width) {
- s.push("\n");
- x = 0;
- }
- x += l * letterWidth;
- s.push(words[i] + " ");
- }
- t.attr({
- "text" : s.join("")
- });
- var b = t.getBBox()
- , h = Math.abs(b.y2) - Math.abs(b.y) + 1;
- t.attr({
- "y": b.y + h
- });
-}
diff --git a/vendor/assets/javascripts/jquery.blockUI.js b/vendor/assets/javascripts/jquery.blockUI.js
new file mode 100644
index 00000000000..c8702d79b65
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.blockUI.js
@@ -0,0 +1,590 @@
+/*!
+ * jQuery blockUI plugin
+ * Version 2.60.0-2013.04.05
+ * @requires jQuery v1.7 or later
+ *
+ * Examples at: http://malsup.com/jquery/block/
+ * Copyright (c) 2007-2013 M. Alsup
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Thanks to Amir-Hossein Sobhi for some excellent contributions!
+ */
+
+;(function() {
+/*jshint eqeqeq:false curly:false latedef:false */
+"use strict";
+
+ function setup($) {
+ $.fn._fadeIn = $.fn.fadeIn;
+
+ var noOp = $.noop || function() {};
+
+ // this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
+ // retarded userAgent strings on Vista)
+ var msie = /MSIE/.test(navigator.userAgent);
+ var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
+ var mode = document.documentMode || 0;
+ var setExpr = $.isFunction( document.createElement('div').style.setExpression );
+
+ // global $ methods for blocking/unblocking the entire page
+ $.blockUI = function(opts) { install(window, opts); };
+ $.unblockUI = function(opts) { remove(window, opts); };
+
+ // convenience method for quick growl-like notifications (http://www.google.com/search?q=growl)
+ $.growlUI = function(title, message, timeout, onClose) {
+ var $m = $('<div class="growlUI"></div>');
+ if (title) $m.append('<h1>'+title+'</h1>');
+ if (message) $m.append('<h2>'+message+'</h2>');
+ if (timeout === undefined) timeout = 3000;
+ $.blockUI({
+ message: $m, fadeIn: 700, fadeOut: 1000, centerY: false,
+ timeout: timeout, showOverlay: false,
+ onUnblock: onClose,
+ css: $.blockUI.defaults.growlCSS
+ });
+ };
+
+ // plugin method for blocking element content
+ $.fn.block = function(opts) {
+ if ( this[0] === window ) {
+ $.blockUI( opts );
+ return this;
+ }
+ var fullOpts = $.extend({}, $.blockUI.defaults, opts || {});
+ this.each(function() {
+ var $el = $(this);
+ if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked'))
+ return;
+ $el.unblock({ fadeOut: 0 });
+ });
+
+ return this.each(function() {
+ if ($.css(this,'position') == 'static') {
+ this.style.position = 'relative';
+ $(this).data('blockUI.static', true);
+ }
+ this.style.zoom = 1; // force 'hasLayout' in ie
+ install(this, opts);
+ });
+ };
+
+ // plugin method for unblocking element content
+ $.fn.unblock = function(opts) {
+ if ( this[0] === window ) {
+ $.unblockUI( opts );
+ return this;
+ }
+ return this.each(function() {
+ remove(this, opts);
+ });
+ };
+
+ $.blockUI.version = 2.60; // 2nd generation blocking at no extra cost!
+
+ // override these in your code to change the default behavior and style
+ $.blockUI.defaults = {
+ // message displayed when blocking (use null for no message)
+ message: '<h1>Please wait...</h1>',
+
+ title: null, // title string; only used when theme == true
+ draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded)
+
+ theme: false, // set to true to use with jQuery UI themes
+
+ // styles for the message when blocking; if you wish to disable
+ // these and use an external stylesheet then do this in your code:
+ // $.blockUI.defaults.css = {};
+ css: {
+ padding: 0,
+ margin: 0,
+ width: '30%',
+ top: '40%',
+ left: '35%',
+ textAlign: 'center',
+ color: '#000',
+ border: '3px solid #aaa',
+ backgroundColor:'#fff',
+ cursor: 'wait'
+ },
+
+ // minimal style set used when themes are used
+ themedCSS: {
+ width: '30%',
+ top: '40%',
+ left: '35%'
+ },
+
+ // styles for the overlay
+ overlayCSS: {
+ backgroundColor: '#000',
+ opacity: 0.6,
+ cursor: 'wait'
+ },
+
+ // style to replace wait cursor before unblocking to correct issue
+ // of lingering wait cursor
+ cursorReset: 'default',
+
+ // styles applied when using $.growlUI
+ growlCSS: {
+ width: '350px',
+ top: '10px',
+ left: '',
+ right: '10px',
+ border: 'none',
+ padding: '5px',
+ opacity: 0.6,
+ cursor: 'default',
+ color: '#fff',
+ backgroundColor: '#000',
+ '-webkit-border-radius':'10px',
+ '-moz-border-radius': '10px',
+ 'border-radius': '10px'
+ },
+
+ // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
+ // (hat tip to Jorge H. N. de Vasconcelos)
+ /*jshint scripturl:true */
+ iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
+
+ // force usage of iframe in non-IE browsers (handy for blocking applets)
+ forceIframe: false,
+
+ // z-index for the blocking overlay
+ baseZ: 1000,
+
+ // set these to true to have the message automatically centered
+ centerX: true, // <-- only effects element blocking (page block controlled via css above)
+ centerY: true,
+
+ // allow body element to be stetched in ie6; this makes blocking look better
+ // on "short" pages. disable if you wish to prevent changes to the body height
+ allowBodyStretch: true,
+
+ // enable if you want key and mouse events to be disabled for content that is blocked
+ bindEvents: true,
+
+ // be default blockUI will supress tab navigation from leaving blocking content
+ // (if bindEvents is true)
+ constrainTabKey: true,
+
+ // fadeIn time in millis; set to 0 to disable fadeIn on block
+ fadeIn: 200,
+
+ // fadeOut time in millis; set to 0 to disable fadeOut on unblock
+ fadeOut: 400,
+
+ // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
+ timeout: 0,
+
+ // disable if you don't want to show the overlay
+ showOverlay: true,
+
+ // if true, focus will be placed in the first available input field when
+ // page blocking
+ focusInput: true,
+
+ // elements that can receive focus
+ focusableElements: ':input:enabled:visible',
+
+ // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
+ // no longer needed in 2012
+ // applyPlatformOpacityRules: true,
+
+ // callback method invoked when fadeIn has completed and blocking message is visible
+ onBlock: null,
+
+ // callback method invoked when unblocking has completed; the callback is
+ // passed the element that has been unblocked (which is the window object for page
+ // blocks) and the options that were passed to the unblock call:
+ // onUnblock(element, options)
+ onUnblock: null,
+
+ // callback method invoked when the overlay area is clicked.
+ // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used.
+ onOverlayClick: null,
+
+ // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
+ quirksmodeOffsetHack: 4,
+
+ // class name of the message block
+ blockMsgClass: 'blockMsg',
+
+ // if it is already blocked, then ignore it (don't unblock and reblock)
+ ignoreIfBlocked: false
+ };
+
+ // private data and functions follow...
+
+ var pageBlock = null;
+ var pageBlockEls = [];
+
+ function install(el, opts) {
+ var css, themedCSS;
+ var full = (el == window);
+ var msg = (opts && opts.message !== undefined ? opts.message : undefined);
+ opts = $.extend({}, $.blockUI.defaults, opts || {});
+
+ if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked'))
+ return;
+
+ opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
+ css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
+ if (opts.onOverlayClick)
+ opts.overlayCSS.cursor = 'pointer';
+
+ themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
+ msg = msg === undefined ? opts.message : msg;
+
+ // remove the current block (if there is one)
+ if (full && pageBlock)
+ remove(window, {fadeOut:0});
+
+ // if an existing element is being used as the blocking content then we capture
+ // its current place in the DOM (and current display style) so we can restore
+ // it when we unblock
+ if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
+ var node = msg.jquery ? msg[0] : msg;
+ var data = {};
+ $(el).data('blockUI.history', data);
+ data.el = node;
+ data.parent = node.parentNode;
+ data.display = node.style.display;
+ data.position = node.style.position;
+ if (data.parent)
+ data.parent.removeChild(node);
+ }
+
+ $(el).data('blockUI.onUnblock', opts.onUnblock);
+ var z = opts.baseZ;
+
+ // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
+ // layer1 is the iframe layer which is used to supress bleed through of underlying content
+ // layer2 is the overlay layer which has opacity and a wait cursor (by default)
+ // layer3 is the message content that is displayed while blocking
+ var lyr1, lyr2, lyr3, s;
+ if (msie || opts.forceIframe)
+ lyr1 = $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>');
+ else
+ lyr1 = $('<div class="blockUI" style="display:none"></div>');
+
+ if (opts.theme)
+ lyr2 = $('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+ (z++) +';display:none"></div>');
+ else
+ lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
+
+ if (opts.theme && full) {
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:fixed">';
+ if ( opts.title ) {
+ s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
+ }
+ s += '<div class="ui-widget-content ui-dialog-content"></div>';
+ s += '</div>';
+ }
+ else if (opts.theme) {
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:absolute">';
+ if ( opts.title ) {
+ s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
+ }
+ s += '<div class="ui-widget-content ui-dialog-content"></div>';
+ s += '</div>';
+ }
+ else if (full) {
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:'+(z+10)+';display:none;position:fixed"></div>';
+ }
+ else {
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:'+(z+10)+';display:none;position:absolute"></div>';
+ }
+ lyr3 = $(s);
+
+ // if we have a message, style it
+ if (msg) {
+ if (opts.theme) {
+ lyr3.css(themedCSS);
+ lyr3.addClass('ui-widget-content');
+ }
+ else
+ lyr3.css(css);
+ }
+
+ // style the overlay
+ if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/)
+ lyr2.css(opts.overlayCSS);
+ lyr2.css('position', full ? 'fixed' : 'absolute');
+
+ // make iframe layer transparent in IE
+ if (msie || opts.forceIframe)
+ lyr1.css('opacity',0.0);
+
+ //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
+ var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
+ $.each(layers, function() {
+ this.appendTo($par);
+ });
+
+ if (opts.theme && opts.draggable && $.fn.draggable) {
+ lyr3.draggable({
+ handle: '.ui-dialog-titlebar',
+ cancel: 'li'
+ });
+ }
+
+ // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
+ var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0);
+ if (ie6 || expr) {
+ // give body 100% height
+ if (full && opts.allowBodyStretch && $.support.boxModel)
+ $('html,body').css('height','100%');
+
+ // fix ie6 issue when blocked element has a border width
+ if ((ie6 || !$.support.boxModel) && !full) {
+ var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
+ var fixT = t ? '(0 - '+t+')' : 0;
+ var fixL = l ? '(0 - '+l+')' : 0;
+ }
+
+ // simulate fixed position
+ $.each(layers, function(i,o) {
+ var s = o[0].style;
+ s.position = 'absolute';
+ if (i < 2) {
+ if (full)
+ s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"');
+ else
+ s.setExpression('height','this.parentNode.offsetHeight + "px"');
+ if (full)
+ s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"');
+ else
+ s.setExpression('width','this.parentNode.offsetWidth + "px"');
+ if (fixL) s.setExpression('left', fixL);
+ if (fixT) s.setExpression('top', fixT);
+ }
+ else if (opts.centerY) {
+ if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
+ s.marginTop = 0;
+ }
+ else if (!opts.centerY && full) {
+ var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0;
+ var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
+ s.setExpression('top',expression);
+ }
+ });
+ }
+
+ // show the message
+ if (msg) {
+ if (opts.theme)
+ lyr3.find('.ui-widget-content').append(msg);
+ else
+ lyr3.append(msg);
+ if (msg.jquery || msg.nodeType)
+ $(msg).show();
+ }
+
+ if ((msie || opts.forceIframe) && opts.showOverlay)
+ lyr1.show(); // opacity is zero
+ if (opts.fadeIn) {
+ var cb = opts.onBlock ? opts.onBlock : noOp;
+ var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
+ var cb2 = msg ? cb : noOp;
+ if (opts.showOverlay)
+ lyr2._fadeIn(opts.fadeIn, cb1);
+ if (msg)
+ lyr3._fadeIn(opts.fadeIn, cb2);
+ }
+ else {
+ if (opts.showOverlay)
+ lyr2.show();
+ if (msg)
+ lyr3.show();
+ if (opts.onBlock)
+ opts.onBlock();
+ }
+
+ // bind key and mouse events
+ bind(1, el, opts);
+
+ if (full) {
+ pageBlock = lyr3[0];
+ pageBlockEls = $(opts.focusableElements,pageBlock);
+ if (opts.focusInput)
+ setTimeout(focus, 20);
+ }
+ else
+ center(lyr3[0], opts.centerX, opts.centerY);
+
+ if (opts.timeout) {
+ // auto-unblock
+ var to = setTimeout(function() {
+ if (full)
+ $.unblockUI(opts);
+ else
+ $(el).unblock(opts);
+ }, opts.timeout);
+ $(el).data('blockUI.timeout', to);
+ }
+ }
+
+ // remove the block
+ function remove(el, opts) {
+ var count;
+ var full = (el == window);
+ var $el = $(el);
+ var data = $el.data('blockUI.history');
+ var to = $el.data('blockUI.timeout');
+ if (to) {
+ clearTimeout(to);
+ $el.removeData('blockUI.timeout');
+ }
+ opts = $.extend({}, $.blockUI.defaults, opts || {});
+ bind(0, el, opts); // unbind events
+
+ if (opts.onUnblock === null) {
+ opts.onUnblock = $el.data('blockUI.onUnblock');
+ $el.removeData('blockUI.onUnblock');
+ }
+
+ var els;
+ if (full) // crazy selector to handle odd field errors in ie6/7
+ els = $('body').children().filter('.blockUI').add('body > .blockUI');
+ else
+ els = $el.find('>.blockUI');
+
+ // fix cursor issue
+ if ( opts.cursorReset ) {
+ if ( els.length > 1 )
+ els[1].style.cursor = opts.cursorReset;
+ if ( els.length > 2 )
+ els[2].style.cursor = opts.cursorReset;
+ }
+
+ if (full)
+ pageBlock = pageBlockEls = null;
+
+ if (opts.fadeOut) {
+ count = els.length;
+ els.fadeOut(opts.fadeOut, function() {
+ if ( --count === 0)
+ reset(els,data,opts,el);
+ });
+ }
+ else
+ reset(els, data, opts, el);
+ }
+
+ // move blocking element back into the DOM where it started
+ function reset(els,data,opts,el) {
+ var $el = $(el);
+ els.each(function(i,o) {
+ // remove via DOM calls so we don't lose event handlers
+ if (this.parentNode)
+ this.parentNode.removeChild(this);
+ });
+
+ if (data && data.el) {
+ data.el.style.display = data.display;
+ data.el.style.position = data.position;
+ if (data.parent)
+ data.parent.appendChild(data.el);
+ $el.removeData('blockUI.history');
+ }
+
+ if ($el.data('blockUI.static')) {
+ $el.css('position', 'static'); // #22
+ }
+
+ if (typeof opts.onUnblock == 'function')
+ opts.onUnblock(el,opts);
+
+ // fix issue in Safari 6 where block artifacts remain until reflow
+ var body = $(document.body), w = body.width(), cssW = body[0].style.width;
+ body.width(w-1).width(w);
+ body[0].style.width = cssW;
+ }
+
+ // bind/unbind the handler
+ function bind(b, el, opts) {
+ var full = el == window, $el = $(el);
+
+ // don't bother unbinding if there is nothing to unbind
+ if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
+ return;
+
+ $el.data('blockUI.isBlocked', b);
+
+ // don't bind events when overlay is not in use or if bindEvents is false
+ if (!full || !opts.bindEvents || (b && !opts.showOverlay))
+ return;
+
+ // bind anchors and inputs for mouse and key events
+ var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove';
+ if (b)
+ $(document).bind(events, opts, handler);
+ else
+ $(document).unbind(events, handler);
+
+ // former impl...
+ // var $e = $('a,:input');
+ // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
+ }
+
+ // event handler to suppress keyboard/mouse events when blocking
+ function handler(e) {
+ // allow tab navigation (conditionally)
+ if (e.keyCode && e.keyCode == 9) {
+ if (pageBlock && e.data.constrainTabKey) {
+ var els = pageBlockEls;
+ var fwd = !e.shiftKey && e.target === els[els.length-1];
+ var back = e.shiftKey && e.target === els[0];
+ if (fwd || back) {
+ setTimeout(function(){focus(back);},10);
+ return false;
+ }
+ }
+ }
+ var opts = e.data;
+ var target = $(e.target);
+ if (target.hasClass('blockOverlay') && opts.onOverlayClick)
+ opts.onOverlayClick();
+
+ // allow events within the message content
+ if (target.parents('div.' + opts.blockMsgClass).length > 0)
+ return true;
+
+ // allow events for content that is not being blocked
+ return target.parents().children().filter('div.blockUI').length === 0;
+ }
+
+ function focus(back) {
+ if (!pageBlockEls)
+ return;
+ var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
+ if (e)
+ e.focus();
+ }
+
+ function center(el, x, y) {
+ var p = el.parentNode, s = el.style;
+ var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
+ var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
+ if (x) s.left = l > 0 ? (l+'px') : '0';
+ if (y) s.top = t > 0 ? (t+'px') : '0';
+ }
+
+ function sz(el, p) {
+ return parseInt($.css(el,p),10)||0;
+ }
+
+ }
+
+
+ /*global define:true */
+ if (typeof define === 'function' && define.amd && define.amd.jQuery) {
+ define(['jquery'], setup);
+ } else {
+ setup(jQuery);
+ }
+
+})();