summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG29
-rw-r--r--CONTRIBUTING.md53
-rw-r--r--Gemfile31
-rw-r--r--Gemfile.lock78
-rw-r--r--README.md50
-rw-r--r--VERSION2
-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/monokai-scheme-preview.png (renamed from app/assets/images/monokai.png)bin6651 -> 6651 bytes
-rw-r--r--app/assets/images/solarized-dark-scheme-preview.png (renamed from app/assets/images/solarized_dark.png)bin16320 -> 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/admin.js.coffee6
-rw-r--r--app/assets/javascripts/api.js.coffee2
-rw-r--r--app/assets/javascripts/application.js2
-rw-r--r--app/assets/javascripts/commits.js.coffee1
-rw-r--r--app/assets/javascripts/dashboard.js.coffee2
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee6
-rw-r--r--app/assets/javascripts/main.js.coffee4
-rw-r--r--app/assets/javascripts/milestones.js.coffee14
-rw-r--r--app/assets/javascripts/notes.js147
-rw-r--r--app/assets/javascripts/pager.js.coffee1
-rw-r--r--app/assets/javascripts/stat_graph.js.coffee6
-rw-r--r--app/assets/javascripts/stat_graph_contributors.js.coffee61
-rw-r--r--app/assets/javascripts/stat_graph_contributors_graph.js.coffee166
-rw-r--r--app/assets/javascripts/stat_graph_contributors_util.js.coffee91
-rw-r--r--app/assets/javascripts/team_members.js.coffee6
-rw-r--r--app/assets/javascripts/users_select.js.coffee35
-rw-r--r--app/assets/stylesheets/application.scss1
-rw-r--r--app/assets/stylesheets/common.scss10
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/avatar.scss1
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/nav.scss12
-rw-r--r--app/assets/stylesheets/highlight/dark.scss2
-rw-r--r--app/assets/stylesheets/sections/events.scss8
-rw-r--r--app/assets/stylesheets/sections/graph.scss1
-rw-r--r--app/assets/stylesheets/sections/header.scss1
-rw-r--r--app/assets/stylesheets/sections/issues.scss4
-rw-r--r--app/assets/stylesheets/sections/notes.scss45
-rw-r--r--app/assets/stylesheets/sections/stat_graph.scss48
-rw-r--r--app/assets/stylesheets/selects.scss4
-rw-r--r--app/contexts/issues/list_context.rb2
-rw-r--r--app/contexts/merge_requests_load_context.rb2
-rw-r--r--app/contexts/projects/create_context.rb1
-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.rb9
-rw-r--r--app/controllers/admin/projects_controller.rb9
-rw-r--r--app/controllers/admin/resque_controller.rb4
-rw-r--r--app/controllers/admin/teams/projects_controller.rb2
-rw-r--r--app/controllers/admin/users_controller.rb10
-rw-r--r--app/controllers/application_controller.rb20
-rw-r--r--app/controllers/commit_controller.rb6
-rw-r--r--app/controllers/dashboard_controller.rb2
-rw-r--r--app/controllers/deploy_keys_controller.rb2
-rw-r--r--app/controllers/graphs_controller.rb17
-rw-r--r--app/controllers/groups_controller.rb20
-rw-r--r--app/controllers/help_controller.rb11
-rw-r--r--app/controllers/network_controller.rb (renamed from app/controllers/graph_controller.rb)2
-rw-r--r--app/controllers/notes_controller.rb26
-rw-r--r--app/controllers/passwords_controller.rb38
-rw-r--r--app/controllers/profiles_controller.rb15
-rw-r--r--app/controllers/projects/application_controller.rb11
-rw-r--r--app/controllers/projects/snippets_controller.rb91
-rw-r--r--app/controllers/projects/teams_controller.rb7
-rw-r--r--app/controllers/projects_controller.rb18
-rw-r--r--app/controllers/refs_controller.rb2
-rw-r--r--app/controllers/registrations_controller.rb3
-rw-r--r--app/controllers/snippets_controller.rb77
-rw-r--r--app/controllers/teams_controller.rb18
-rw-r--r--app/controllers/users_controller.rb4
-rw-r--r--app/helpers/application_helper.rb45
-rw-r--r--app/helpers/events_helper.rb36
-rw-r--r--app/helpers/issues_helper.rb2
-rw-r--r--app/helpers/namespaces_helper.rb10
-rw-r--r--app/helpers/notes_helper.rb7
-rw-r--r--app/helpers/oauth_helper.rb6
-rw-r--r--app/helpers/projects_helper.rb38
-rw-r--r--app/helpers/snippets_helper.rb8
-rw-r--r--app/helpers/tab_helper.rb2
-rw-r--r--app/helpers/tree_helper.rb6
-rw-r--r--app/models/ability.rb41
-rw-r--r--app/models/concerns/issuable.rb19
-rw-r--r--app/models/concerns/mentionable.rb37
-rw-r--r--app/models/event.rb8
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/key.rb2
-rw-r--r--app/models/merge_request.rb9
-rw-r--r--app/models/namespace.rb1
-rw-r--r--app/models/network/graph.rb2
-rw-r--r--app/models/note.rb8
-rw-r--r--app/models/personal_snippet.rb18
-rw-r--r--app/models/project.rb25
-rw-r--r--app/models/project_snippet.rb27
-rw-r--r--app/models/snippet.rb14
-rw-r--r--app/models/user.rb45
-rw-r--r--app/models/user_team.rb3
-rw-r--r--app/observers/base_observer.rb4
-rw-r--r--app/observers/issue_observer.rb2
-rw-r--r--app/observers/merge_request_observer.rb2
-rw-r--r--app/observers/project_observer.rb20
-rw-r--r--app/observers/user_observer.rb11
-rw-r--r--app/services/notification_service.rb15
-rw-r--r--app/views/admin/background_jobs/show.html.haml (renamed from app/views/admin/resque/show.html.haml)0
-rw-r--r--app/views/admin/dashboard/index.html.haml24
-rw-r--r--app/views/admin/groups/show.html.haml187
-rw-r--r--app/views/admin/hooks/_data_ex.html.erb29
-rw-r--r--app/views/admin/projects/index.html.haml8
-rw-r--r--app/views/admin/teams/index.html.haml65
-rw-r--r--app/views/admin/teams/members/new.html.haml54
-rw-r--r--app/views/admin/teams/projects/new.html.haml35
-rw-r--r--app/views/admin/teams/show.html.haml175
-rw-r--r--app/views/admin/users/_form.html.haml30
-rw-r--r--app/views/admin/users/show.html.haml114
-rw-r--r--app/views/dashboard/projects.html.haml30
-rw-r--r--app/views/deploy_keys/_deploy_key.html.haml4
-rw-r--r--app/views/deploy_keys/index.html.haml2
-rw-r--r--app/views/devise/sessions/_oauth_providers.html.haml5
-rw-r--r--app/views/edit_tree/show.html.haml6
-rw-r--r--app/views/events/_commit.html.haml2
-rw-r--r--app/views/events/_event.html.haml2
-rw-r--r--app/views/events/event/_note.html.haml16
-rw-r--r--app/views/graphs/show.html.haml31
-rw-r--r--app/views/graphs/show.js.haml16
-rw-r--r--app/views/groups/_new_group_member.html.haml2
-rw-r--r--app/views/groups/_new_member.html.haml2
-rw-r--r--app/views/groups/edit.html.haml6
-rw-r--r--app/views/groups/new.html.haml18
-rw-r--r--app/views/groups/people.html.haml2
-rw-r--r--app/views/groups/show.atom.builder4
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/help/_api_layout.html.haml13
-rw-r--r--app/views/help/api.html.haml127
-rw-r--r--app/views/help/index.html.haml4
-rw-r--r--app/views/help/markdown.html.haml126
-rw-r--r--app/views/help/permissions.html.haml2
-rw-r--r--app/views/issues/_form.html.haml66
-rw-r--r--app/views/issues/_issues.html.haml6
-rw-r--r--app/views/issues/show.html.haml5
-rw-r--r--app/views/layouts/_head_panel.html.haml6
-rw-r--r--app/views/layouts/nav/_admin.html.haml4
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml2
-rw-r--r--app/views/layouts/nav/_project.html.haml37
-rw-r--r--app/views/layouts/nav/_team.html.haml2
-rw-r--r--app/views/layouts/navless.html.haml10
-rw-r--r--app/views/layouts/public.html.haml29
-rw-r--r--app/views/layouts/search.html.haml10
-rw-r--r--app/views/milestones/_issues.html.haml11
-rw-r--r--app/views/milestones/_merge_request.html.haml5
-rw-r--r--app/views/milestones/show.html.haml79
-rw-r--r--app/views/network/_head.html.haml (renamed from app/views/graph/_head.html.haml)4
-rw-r--r--app/views/network/show.html.haml (renamed from app/views/graph/show.html.haml)2
-rw-r--r--app/views/network/show.json.erb (renamed from app/views/graph/show.json.erb)0
-rw-r--r--app/views/notes/_discussion.html.haml2
-rw-r--r--app/views/notes/_note.html.haml51
-rw-r--r--app/views/notify/new_user_email.html.haml9
-rw-r--r--app/views/notify/new_user_email.text.erb5
-rw-r--r--app/views/passwords/new.html.haml22
-rw-r--r--app/views/profiles/account.html.haml4
-rw-r--r--app/views/profiles/design.html.haml26
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--app/views/projects/_clone_panel.html.haml5
-rw-r--r--app/views/projects/_settings_nav.html.haml6
-rw-r--r--app/views/projects/fork.html.haml19
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/show.html.haml3
-rw-r--r--app/views/projects/snippets/_blob.html.haml15
-rw-r--r--app/views/projects/snippets/_form.html.haml41
-rw-r--r--app/views/projects/snippets/_snippet.html.haml25
-rw-r--r--app/views/projects/snippets/edit.html.haml1
-rw-r--r--app/views/projects/snippets/index.html.haml13
-rw-r--r--app/views/projects/snippets/new.html.haml1
-rw-r--r--app/views/projects/snippets/show.html.haml13
-rw-r--r--app/views/projects/teams/available.html.haml2
-rw-r--r--app/views/protected_branches/index.html.haml2
-rw-r--r--app/views/repositories/_branch.html.haml2
-rw-r--r--app/views/repositories/_feed.html.haml2
-rw-r--r--app/views/repositories/stats.html.haml2
-rw-r--r--app/views/shared/_clone_panel.html.haml10
-rw-r--r--app/views/snippets/_blob.html.haml6
-rw-r--r--app/views/snippets/_form.html.haml9
-rw-r--r--app/views/snippets/_snippet.html.haml41
-rw-r--r--app/views/snippets/_snippets.html.haml7
-rw-r--r--app/views/snippets/current_user_index.html.haml24
-rw-r--r--app/views/snippets/edit.html.haml2
-rw-r--r--app/views/snippets/index.html.haml26
-rw-r--r--app/views/snippets/new.html.haml2
-rw-r--r--app/views/snippets/show.html.haml15
-rw-r--r--app/views/snippets/user_index.html.haml13
-rw-r--r--app/views/team_members/_team.html.haml3
-rw-r--r--app/views/team_members/_team_member.html.haml4
-rw-r--r--app/views/team_members/index.html.haml2
-rw-r--r--app/views/teams/edit.html.haml5
-rw-r--r--app/views/teams/members/_member.html.haml (renamed from app/views/teams/members/_show.html.haml)16
-rw-r--r--app/views/teams/members/_team.html.haml20
-rw-r--r--app/views/teams/members/index.html.haml26
-rw-r--r--app/views/teams/new.html.haml16
-rw-r--r--app/views/teams/show.atom.builder4
-rw-r--r--app/views/teams/show.html.haml2
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--config/database.yml.mysql2
-rw-r--r--config/database.yml.postgresql2
-rw-r--r--config/environments/production.rb2
-rw-r--r--config/gitlab.yml.example20
-rw-r--r--config/initializers/1_settings.rb22
-rw-r--r--config/initializers/2_app.rb5
-rw-r--r--config/initializers/smtp_settings.rb.sample2
-rw-r--r--config/puma.rb.example7
-rw-r--r--config/routes.rb41
-rw-r--r--config/unicorn.rb.example102
-rw-r--r--db/fixtures/development/09_issues.rb2
-rw-r--r--db/fixtures/development/10_merge_requests.rb3
-rw-r--r--db/fixtures/production/001_admin.rb3
-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/20130326142630_add_index_to_users_authentication_token.rb5
-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/schema.rb16
-rw-r--r--doc/api/README.md27
-rw-r--r--doc/api/deploy_keys.md87
-rw-r--r--doc/api/issues.md12
-rw-r--r--doc/api/milestones.md2
-rw-r--r--doc/api/project_snippets.md (renamed from doc/api/snippets.md)0
-rw-r--r--doc/api/projects.md194
-rw-r--r--doc/api/repositories.md50
-rw-r--r--doc/api/user_teams.md209
-rw-r--r--doc/api/users.md16
-rw-r--r--doc/install/databases.md19
-rw-r--r--doc/install/installation.md127
-rw-r--r--doc/make_release.md56
-rw-r--r--doc/markdown/markdown.md451
-rw-r--r--doc/raketasks/maintenance.md5
-rw-r--r--doc/update/5.0-to-5.1.md7
-rw-r--r--doc/update/5.1-to-5.2.md68
-rw-r--r--doc/update/5.2-to-5.3.md80
-rw-r--r--features/admin/groups.feature1
-rw-r--r--features/dashboard/active_tab.feature5
-rw-r--r--features/group/group.feature1
-rw-r--r--features/project/graph.feature9
-rw-r--r--features/project/issues/milestones.feature2
-rw-r--r--features/project/project.feature1
-rw-r--r--features/project/snippets.feature35
-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.rb5
-rw-r--r--features/steps/admin/admin_teams.rb2
-rw-r--r--features/steps/dashboard/dashboard_active_tab.rb4
-rw-r--r--features/steps/group/group.rb3
-rw-r--r--features/steps/profile/profile.rb2
-rw-r--r--features/steps/project/deploy_keys.rb1
-rw-r--r--features/steps/project/project_active_tab.rb8
-rw-r--r--features/steps/project/project_graph.rb13
-rw-r--r--features/steps/project/project_merge_requests.rb4
-rw-r--r--features/steps/project/project_milestones.rb8
-rw-r--r--features/steps/project/project_network_graph.rb2
-rw-r--r--features/steps/project/project_snippets.rb100
-rw-r--r--features/steps/shared/paths.rb20
-rw-r--r--features/steps/shared/snippet.rb21
-rw-r--r--features/steps/snippets/discover_snippets.rb17
-rw-r--r--features/steps/snippets/snippets.rb65
-rw-r--r--features/steps/snippets/user_snippets.rb41
-rw-r--r--features/steps/userteams/userteams.rb30
-rw-r--r--lib/api/api.rb4
-rw-r--r--lib/api/deploy_keys.rb84
-rw-r--r--lib/api/entities.rb37
-rw-r--r--lib/api/helpers.rb6
-rw-r--r--lib/api/issues.rb5
-rw-r--r--lib/api/merge_requests.rb3
-rw-r--r--lib/api/milestones.rb2
-rw-r--r--lib/api/project_hooks.rb108
-rw-r--r--lib/api/project_snippets.rb123
-rw-r--r--lib/api/projects.rb297
-rw-r--r--lib/api/repositories.rb29
-rw-r--r--lib/api/user_teams.rb276
-rw-r--r--lib/backup/repository.rb2
-rw-r--r--lib/backup/uploads.rb8
-rw-r--r--lib/gitlab/auth.rb3
-rw-r--r--lib/gitlab/backend/grack_auth.rb141
-rw-r--r--lib/gitlab/backend/grack_helpers.rb28
-rw-r--r--lib/gitlab/backend/grack_ldap.rb24
-rw-r--r--lib/gitlab/blacklist.rb9
-rw-r--r--lib/gitlab/inline_diff.rb7
-rw-r--r--lib/gitlab/markdown.rb3
-rw-r--r--lib/gitlab/user_team_manager.rb53
-rw-r--r--lib/gitlab/version_info.rb2
-rw-r--r--lib/redcarpet/render/gitlab_html.rb3
-rw-r--r--lib/support/init.d/gitlab4
-rw-r--r--lib/support/nginx/gitlab3
-rw-r--r--lib/tasks/gitlab/backup.rake22
-rw-r--r--lib/tasks/gitlab/check.rake12
-rw-r--r--lib/tasks/gitlab/import.rake47
-rw-r--r--lib/tasks/gitlab/shell.rake10
-rwxr-xr-xscript/check2
-rw-r--r--spec/factories.rb23
-rw-r--r--spec/features/admin/admin_users_spec.rb26
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb68
-rw-r--r--spec/features/snippets_spec.rb99
-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.rb39
-rw-r--r--spec/javascripts/helpers/.gitkeep0
-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.rb10
-rw-r--r--spec/lib/gitlab/user_team_manager_spec.rb28
-rw-r--r--spec/mailers/notify_spec.rb8
-rw-r--r--spec/models/milestone_spec.rb1
-rw-r--r--spec/models/project_snippet_spec.rb30
-rw-r--r--spec/models/project_spec.rb2
-rw-r--r--spec/models/snippet_spec.rb3
-rw-r--r--spec/models/user_spec.rb58
-rw-r--r--spec/requests/api/notes_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb92
-rw-r--r--spec/requests/api/repositories_spec.rb23
-rw-r--r--spec/requests/api/user_teams_spec.rb360
-rw-r--r--spec/routing/admin_routing_spec.rb6
-rw-r--r--spec/routing/project_routing_spec.rb46
-rw-r--r--spec/routing/routing_spec.rb51
-rw-r--r--spec/services/notification_service_spec.rb5
324 files changed, 6701 insertions, 2025 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 97fd6e9f3d0..c20f27ad7db 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,13 +1,40 @@
+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 suppoort of unicorn and fog gems
+ - Added client list to API doc
+
v 5.3.0
- Refactored services
- Campfire service added
- HipChat service added
- Fixed bug with LDAP + git over http
- - Fixed bug with googla analytics code being ignored
+ - 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: projets/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
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8d0027a92c4..2a6eb71b654 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,55 +4,66 @@ This guide details how to use issues and pull requests to improve GitLab.
## Closing policy for issues and pull requests
-Issues and pull requests not in line with the guidelines listed in this document will be closed with just a link to this paragraph. GitLab is a popular open source project and the capacity to deal with issues and pull requests is limited. To get support for your problems please use other 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/).
+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
-The [issue tracker](https://github.com/gitlabhq/gitlabhq/issues) is only for obvious bugs or misbehavior in the master branch of GitLab. When submitting an issue please conform to the issue submission guidelines listed below.
+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.
+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 [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) 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.
+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 or idea. Show your support with `:+1:` and/or join the discussion.
+**[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):
-* Only report issues for supported versions according to the [maintenance policy](MAINTENANCE.md)
-* Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
-* Describe your issue in detail
-* How can we reproduce the issue on the [GitLab Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) (start with: `vagrant destroy && vagrant up && vagrant ssh`)
-* Add the last commit sha1 of the GitLab version you used to replicate the issue
-* Add logs or screen shots when possible
-* Link to the line of code that might be responsible for the problem
-* Describe your setup (use relevant parts from `sudo -u gitlab -H bundle exec rake gitlab:env:info`)
+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
## Pull requests
-We welcome pull request with improvements to GitLab code and/or documentation. The issues 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.
+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
- If you can please submit a pull request with the fix including tests. The workflow to make a pull request is as follows:
+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:
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 comments
+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:
-* The code has proper tests and all tests pass
+* 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 doesn't break any existing functionality
-* It's quality code that conforms to the [Rails style guide](https://github.com/bbatsov/rails-style-guide) and best practices
+* 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 need the same functionality
+* 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)
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 924d8a27254..03d3f5c1c23 100644
--- a/Gemfile
+++ b/Gemfile
@@ -23,13 +23,13 @@ gem 'omniauth-github'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem 'gitlab_git', '~> 1.2.1'
+gem 'gitlab_git', '~> 1.3.0'
# Ruby/Rack Git Smart-HTTP Server Handler
-gem 'gitlab-grack', '~> 1.0.0', require: 'grack'
+gem 'gitlab-grack', '~> 1.0.1', require: 'grack'
# LDAP Auth
-gem 'gitlab_omniauth-ldap', '1.0.2', require: "omniauth-ldap"
+gem 'gitlab_omniauth-ldap', '1.0.3', require: "omniauth-ldap"
# Syntax highlighter
gem "gitlab-pygments.rb", '~> 0.3.2', require: 'pygments.rb'
@@ -41,8 +41,8 @@ gem "gitlab-gollum-lib", "~> 1.0.0", require: 'gollum-lib'
gem "github-linguist", require: "linguist"
# API
-gem "grape"
-gem "grape-entity"
+gem "grape", "~> 0.4.1"
+gem "grape-entity", "~> 0.3.0"
# Format dates and times
# based on human-friendly examples
@@ -59,8 +59,9 @@ gem "haml-rails"
# Files attachments
gem "carrierwave"
+
# for aws storage
-# gem "fog", "~> 1.3.1"
+gem "fog", "~> 1.3.1", group: :aws
# Authorization
gem "six"
@@ -72,8 +73,12 @@ gem "seed-fu"
gem "redcarpet", "~> 2.2.2"
gem "github-markup", "~> 0.7.4", require: 'github/markup'
+# Asciidoc to HTML
+gem "asciidoctor"
+
# Servers
-gem "puma", '~> 2.0.1'
+gem "puma", '~> 2.3.1', group: :puma
+gem "unicorn", '~> 4.6.3', group: :unicorn
# State machine
gem "state_machine"
@@ -107,6 +112,15 @@ 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"
gem "coffee-rails"
@@ -123,7 +137,7 @@ group :assets do
gem "modernizr", "2.6.2"
gem "raphael-rails", git: "https://github.com/gitlabhq/raphael-rails.git"
gem 'bootstrap-sass'
- gem "font-awesome-sass-rails", "~> 3.0.0"
+ gem "font-awesome-rails", "~> 3.1.1"
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
gem "gon"
end
@@ -177,6 +191,7 @@ group :development, :test do
gem 'poltergeist', '~> 1.3.0'
gem 'spork', '~> 1.0rc'
+ gem 'jasmine'
end
group :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 48be954c0c5..c26eeede69f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -46,6 +46,7 @@ GEM
rails (~> 3.0)
addressable (2.3.4)
arel (3.0.2)
+ asciidoctor (0.1.3)
awesome_print (1.1.0)
backports (2.6.7)
bcrypt-ruby (3.0.1)
@@ -69,6 +70,8 @@ GEM
celluloid (0.14.0)
timers (>= 1.0.0)
charlock_holmes (0.6.9.4)
+ childprocess (0.3.9)
+ ffi (~> 1.0, >= 1.0.11)
chosen-rails (0.9.8)
railties (~> 3.0)
thor (~> 0.14)
@@ -92,6 +95,8 @@ GEM
simplecov (>= 0.7)
thor
crack (0.3.2)
+ d3_rails (3.1.4)
+ railties (>= 3.1.0)
daemons (1.1.9)
database_cleaner (1.0.1)
debug_inspector (0.0.2)
@@ -111,6 +116,7 @@ GEM
erubis (2.7.0)
escape_utils (0.2.4)
eventmachine (1.0.3)
+ excon (0.13.4)
execjs (1.4.0)
multi_json (~> 1.0)
factory_girl (4.2.0)
@@ -126,9 +132,18 @@ GEM
eventmachine (>= 0.12.0)
ffaker (1.16.0)
ffi (1.8.1)
- font-awesome-sass-rails (3.0.2.2)
- railties (>= 3.1.1)
- sass-rails (>= 3.1.1)
+ 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.1.1.3)
+ railties (>= 3.2, < 5.0)
foreman (0.63.0)
dotenv (>= 0.7)
thor (>= 0.13.6)
@@ -150,7 +165,7 @@ GEM
pygments.rb (~> 0.4.2)
sanitize (~> 2.0.3)
stringex (~> 1.5.1)
- gitlab-grack (1.0.0)
+ gitlab-grack (1.0.1)
rack (~> 1.4.1)
gitlab-grit (2.5.1)
charlock_holmes (~> 0.6.9)
@@ -160,13 +175,13 @@ GEM
gitlab-pygments.rb (0.3.2)
posix-spawn (~> 0.3.6)
yajl-ruby (~> 1.1.0)
- gitlab_git (1.2.1)
+ gitlab_git (1.3.0)
activesupport (~> 3.2.13)
github-linguist (~> 2.3.4)
gitlab-grit (~> 2.5.1)
gitlab_meta (5.0)
- gitlab_omniauth-ldap (1.0.2)
- net-ldap (~> 0.2.2)
+ 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)
@@ -216,6 +231,12 @@ GEM
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.3.0)
jquery-rails (2.1.3)
@@ -233,6 +254,7 @@ GEM
kaminari (0.14.1)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
+ kgio (2.8.0)
launchy (2.2.0)
addressable (~> 2.3)
letter_opener (1.1.0)
@@ -256,7 +278,10 @@ GEM
multi_xml (0.5.3)
multipart-post (1.2.0)
mysql2 (0.3.11)
- net-ldap (0.2.2)
+ net-ldap (0.3.1)
+ net-scp (1.0.4)
+ net-ssh (>= 1.99.1)
+ net-ssh (2.6.8)
nokogiri (1.5.9)
oauth (0.4.7)
oauth2 (0.8.1)
@@ -295,7 +320,7 @@ GEM
coderay (~> 1.0.5)
method_source (~> 0.8)
slop (~> 3.4)
- puma (2.0.1)
+ puma (2.3.1)
rack (>= 1.1, < 2.0)
pygments.rb (0.4.2)
posix-spawn (~> 0.3.6)
@@ -344,6 +369,7 @@ GEM
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
+ raindrops (0.11.0)
rake (10.0.4)
rb-fsevent (0.9.3)
rb-inotify (0.9.0)
@@ -390,8 +416,10 @@ GEM
rspec-core (~> 2.13.0)
rspec-expectations (~> 2.13.0)
rspec-mocks (~> 2.13.0)
+ ruby-hmac (0.4.0)
ruby-progressbar (1.0.2)
rubyntlm (0.1.1)
+ rubyzip (0.9.9)
sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6)
sass (3.2.9)
@@ -408,6 +436,11 @@ GEM
select2-rails (3.3.1)
sass-rails (>= 3.2)
thor (~> 0.14)
+ selenium-webdriver (2.32.1)
+ childprocess (>= 0.2.5)
+ multi_json (~> 1.0)
+ rubyzip
+ websocket (~> 1.0.4)
settingslogic (2.0.9)
sexp_processor (4.2.1)
shoulda-matchers (2.1.0)
@@ -482,6 +515,11 @@ GEM
uglifier (2.0.1)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
+ underscore-rails (1.4.4)
+ unicorn (4.6.3)
+ kgio (~> 2.6)
+ rack
+ raindrops (~> 0.7)
virtus (0.5.4)
backports (~> 2.6.1)
descendants_tracker (~> 0.0.1)
@@ -490,6 +528,7 @@ GEM
webmock (1.11.0)
addressable (>= 2.2.7)
crack (>= 0.3.2)
+ websocket (1.0.7)
xpath (2.0.0)
nokogiri (~> 1.3)
yajl-ruby (1.1.0)
@@ -500,6 +539,7 @@ PLATFORMS
DEPENDENCIES
acts-as-taggable-on
annotate!
+ asciidoctor
awesome_print
better_errors
binding_of_caller
@@ -510,32 +550,35 @@ DEPENDENCIES
coffee-rails
colored
coveralls
+ d3_rails (~> 3.1.4)
database_cleaner
devise
email_spec
enumerize
factory_girl_rails
ffaker
- font-awesome-sass-rails (~> 3.0.0)
+ fog (~> 1.3.1)
+ font-awesome-rails (~> 3.1.1)
foreman
gemoji (~> 1.2.1)
github-linguist
github-markup (~> 0.7.4)
gitlab-gollum-lib (~> 1.0.0)
- gitlab-grack (~> 1.0.0)
+ gitlab-grack (~> 1.0.1)
gitlab-pygments.rb (~> 0.3.2)
- gitlab_git (~> 1.2.1)
+ gitlab_git (~> 1.3.0)
gitlab_meta (= 5.0)
- gitlab_omniauth-ldap (= 1.0.2)
+ gitlab_omniauth-ldap (= 1.0.3)
gon
- grape
- grape-entity
+ grape (~> 0.4.1)
+ grape-entity (~> 0.3.0)
growl
guard-rspec
guard-spinach
haml-rails
hipchat (~> 0.9.0)
httparty
+ jasmine
jquery-atwho-rails (= 0.3.0)
jquery-rails (= 2.1.3)
jquery-turbolinks
@@ -553,7 +596,7 @@ DEPENDENCIES
pg
poltergeist (~> 1.3.0)
pry
- puma (~> 2.0.1)
+ puma (~> 2.3.1)
quiet_assets (~> 1.0.1)
rack-mini-profiler
rails (= 3.2.13)
@@ -565,6 +608,7 @@ DEPENDENCIES
redcarpet (~> 2.2.2)
redis-rails
rspec-rails
+ sanitize
sass-rails
sdoc
seed-fu
@@ -586,4 +630,6 @@ DEPENDENCIES
tinder (~> 1.9.2)
turbolinks
uglifier
+ underscore-rails (~> 1.4.4)
+ unicorn (~> 4.6.3)
webmock
diff --git a/README.md b/README.md
index 9135512c571..de773cce0eb 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
![logo](https://raw.github.com/gitlabhq/gitlabhq/master/public/gitlab_logo.png)
+![animated-screenshots](http://makeagif.com/media/6-23-2013/AN3Mo6.gif)
+
### GitLab allows you to
* keep your code secure on your own server
* manage repositories, users and access permissions
@@ -22,15 +24,15 @@
* [![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)
+* [![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.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.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
@@ -43,13 +45,13 @@
* gitlab-shell
* redis
-** More details are in the [requirements doc](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/requirements.md)
+** More details are in the [requirements doc](doc/install/requirements.md)
### Installation
#### Official production installation
-* [Installation guide for a production server](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md)
+* [Installation guide for a production server](doc/install/installation.md)
#### Official development installation
@@ -74,33 +76,33 @@ If you want to contribute, please first read our [Contributing Guidelines](https
Each month on the 22nd a new version is released together with an upgrade guide.
-* [Upgrade guides](https://github.com/gitlabhq/gitlabhq/tree/master/doc/update)
+* [Upgrade guides](doc/update)
-* [Changelog](https://github.com/gitlabhq/gitlabhq/blob/master/CHANGELOG)
+* [Changelog](CHANGELOG)
* Features that will be in the next release are listed on [the feedback and suggestions forum with the status "started"](http://feedback.gitlab.com/forums/176466-general/status/796456).
### Run in production mode
-1. 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:
+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
+ sudo service gitlab start
- or by directly calling the script
+or by directly calling the script
- sudo /etc/init.d/gitlab start
+ 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
+ bundle exec foreman start -p 3000
- or start each component separately
+or start each component separately
- bundle exec rails s
- bundle exec rake sidekiq:start
+ bundle exec rails s
+ bundle exec rake sidekiq:start
### Run the tests
@@ -115,22 +117,26 @@ Start it with [Foreman](https://github.com/ddollar/foreman)
* [RSpec](http://rspec.info/) unit and functional tests
- bundle exec rake spec
+ 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
- bundle exec rake spinach
+ All Spinach tests: bundle exec rake spinach
+
+ Single Spinach test: bundle exec spinach features/project/issues/milestones.feature
### GitLab interfaces
-* [GitLab API](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/README.md)
+* [GitLab API](doc/api/README.md)
-* [Rake tasks](https://github.com/gitlabhq/gitlabhq/tree/master/doc/raketasks)
+* [Rake tasks](doc/raketasks)
-* [Directory structure](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/structure.md)
+* [Directory structure](doc/install/structure.md)
-* [Databases](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/databases.md)
+* [Databases](doc/install/databases.md)
### Getting help
@@ -139,7 +145,7 @@ Start it with [Foreman](https://github.com/ddollar/foreman)
* [Troubleshooting guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) contains solutions to common problems.
-* [Support forum](https://groups.google.com/forum/#!forum/gitlabhq) is the best place 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 had it resolved. 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.
+* [Mailing list](https://groups.google.com/forum/#!forum/gitlabhq) and [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) are the best places to ask questions. For example you can use it if you have questions about: permission denied errors, invisible repos, can't clone/pull/push or with web hooks that don't fire. Please search for similar issues before posting your own, there's a good chance somebody else had the same issue you have now and has resolved it. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there to a fix.
* [Feedback and suggestions forum](http://feedback.gitlab.com) is the place to propose and discuss new features for GitLab.
diff --git a/VERSION b/VERSION
index 91ff57278e3..c5e18371adf 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-5.2.0
+5.4.0.pre
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/monokai.png b/app/assets/images/monokai-scheme-preview.png
index 9477941778e..9477941778e 100644
--- a/app/assets/images/monokai.png
+++ b/app/assets/images/monokai-scheme-preview.png
Binary files differ
diff --git a/app/assets/images/solarized_dark.png b/app/assets/images/solarized-dark-scheme-preview.png
index 728964bc4c8..728964bc4c8 100644
--- a/app/assets/images/solarized_dark.png
+++ 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/admin.js.coffee b/app/assets/javascripts/admin.js.coffee
index c83b74a76a2..da0077ea77b 100644
--- a/app/assets/javascripts/admin.js.coffee
+++ b/app/assets/javascripts/admin.js.coffee
@@ -19,11 +19,13 @@ class Admin
modal = $('.change-owner-holder')
- $('.change-owner-link').bind "click", ->
+ $('.change-owner-link').bind "click", (e) ->
+ e.preventDefault()
$(this).hide()
modal.show()
- $('.change-owner-cancel-link').bind "click", ->
+ $('.change-owner-cancel-link').bind "click", (e) ->
+ e.preventDefault()
modal.hide()
$('.change-owner-link').show()
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index 7cac971f247..db80e7b0f3c 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -50,5 +50,5 @@
callback(users)
buildUrl: (url) ->
- url = gon.relative_url_root + url if gon.relative_url_root.present?
+ 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 ab5fc1b860d..0767b82032d 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -27,3 +27,5 @@
//= require branch-graph
//= require ace-src-noconflict/ace
//= require_tree .
+//= require d3
+//= require underscore
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
index ab8eaa63228..de4c06a2728 100644
--- a/app/assets/javascripts/commits.js.coffee
+++ b/app/assets/javascripts/commits.js.coffee
@@ -42,6 +42,7 @@ class CommitsList
@disable = true
@initLoadMore: ->
+ $(document).unbind('scroll')
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee
index 9beca261467..c2fb95ca635 100644
--- a/app/assets/javascripts/dashboard.js.coffee
+++ b/app/assets/javascripts/dashboard.js.coffee
@@ -27,7 +27,7 @@ class Dashboard
else
event_filters.splice index, 1
- $.cookie "event_filter", event_filters.join(",")
+ $.cookie "event_filter", event_filters.join(","), { path: '/' }
initSidebarTab: ->
key = "dashboard_sidebar_filter"
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 6e2d1592e32..130db5bfdbb 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -1,6 +1,6 @@
$ ->
new Dispatcher()
-
+
class Dispatcher
constructor: () ->
@initSearch()
@@ -10,8 +10,6 @@ class Dispatcher
page = $('body').attr('data-page')
project_id = $('body').attr('data-project-id')
- console.log(page)
-
unless page
return false
@@ -30,6 +28,8 @@ class Dispatcher
new Project()
when 'walls:show'
new Wall(project_id)
+ when 'teams:members:index'
+ new TeamMembers()
switch path.first()
when 'admin' then new Admin()
diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/main.js.coffee
index 65005d6f617..fd7e296efde 100644
--- a/app/assets/javascripts/main.js.coffee
+++ b/app/assets/javascripts/main.js.coffee
@@ -50,7 +50,11 @@ window.startSpinner = ->
window.stopSpinner = ->
$('.turbolink-spinner').fadeOut()
+window.stopEndlessScroll = ->
+ $(document).unbind('scroll')
+
document.addEventListener("page:fetch", startSpinner)
+document.addEventListener("page:fetch", stopEndlessScroll)
document.addEventListener("page:receive", stopSpinner)
$ ->
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/notes.js b/app/assets/javascripts/notes.js
index f5005ec2c94..62961b529fd 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -46,6 +46,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",
@@ -53,12 +73,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() });
},
@@ -97,8 +117,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");
@@ -133,7 +153,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.
*/
@@ -177,6 +197,59 @@ 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();
+
+ 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.
@@ -426,5 +499,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/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..12dfe4da841
--- /dev/null
+++ b/app/assets/javascripts/stat_graph_contributors.js.coffee
@@ -0,0 +1,61 @@
+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 = []
+ _.each(author_data, (d) =>
+ author_header = @create_author_header(d)
+ $(".contributors-list").append(author_header)
+ @authors[d.author] = author_graph = new ContributorsAuthorGraph(d.dates)
+ author_graph.draw()
+ )
+ format_author_commit_info: (author) ->
+ author.commits + " commits " + author.additions + " ++ / " + author.deletions + " --"
+ create_author_header: (author) ->
+ list_item = $('<li/>', {
+ class: 'person'
+ style: 'display: block;'
+ })
+ author_name = $('<h4>' + author.author + '</h4>')
+ author_commit_info_span = $('<span/>', {
+ class: 'commits'
+ })
+ author_commit_info = @format_author_commit_info(author)
+ author_commit_info_span.text(author_commit_info)
+ list_item.append(author_name)
+ 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].list_item).appendTo("ol")
+ @authors[d.author].set_data(d.dates)
+ @authors[d.author].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].list_item)
+ author_commit_info = @format_author_commit_info(author)
+ author_list_item.find("span").text(author_commit_info) \ No newline at end of file
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..e7a120fb572
--- /dev/null
+++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
@@ -0,0 +1,166 @@
+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) ->
+ @width = 1100
+ @height = 125
+ @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")
+ 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) ->
+ y(d.commits = d.commits ? d.additions ? d.deletions)
+ ).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) ->
+ @width = 490
+ @height = 130
+ @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").tickFormat(d3.time.format("%m/%d"));
+ @y_axis = d3.svg.axis().scale(@y).orient("left")
+ 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..8f816313db3
--- /dev/null
+++ b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
@@ -0,0 +1,91 @@
+window.ContributorsStatGraphUtil =
+ parse_log: (log) ->
+ total = {}
+ by_author = {}
+ for entry in log
+ @add_date(entry.date, total) unless total[entry.date]?
+ @add_author(entry.author, by_author) unless by_author[entry.author]?
+ @add_date(entry.date, by_author[entry.author]) unless by_author[entry.author][entry.date]
+ @store_data(entry, total[entry.date], by_author[entry.author][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] = {}
+ by_author[author].author = author
+
+ 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 = log_entry.author
+ parsed_entry.dates = {}
+ parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0
+ _.each(_.omit(log_entry, 'author'), (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
+ \ No newline at end of file
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/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index f9e523ea49f..8286ca2f0c1 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -14,23 +14,24 @@ $ ->
userFormatSelection = (user) ->
user.name
- $('.ajax-users-select').select2
- placeholder: "Search for a user"
- multiple: $('.ajax-users-select').hasClass('multiselect')
- minimumInputLength: 0
- query: (query) ->
- Api.users query.term, (users) ->
- data = { results: users }
- query.callback(data)
+ $('.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)
+ 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
+ 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/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 85e43ed0d35..b1a23427add 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -37,6 +37,7 @@
@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";
diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss
index ccc6f7a9d2d..8c76b4baa22 100644
--- a/app/assets/stylesheets/common.scss
+++ b/app/assets/stylesheets/common.scss
@@ -418,3 +418,13 @@ img.emoji {
overflow: hidden;
height: 220px;
}
+
+.navless-container {
+ margin-top: 30px;
+}
+
+.description-block {
+ @extend .light-well;
+ @extend .light;
+ margin-bottom: 10px;
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/avatar.scss b/app/assets/stylesheets/gitlab_bootstrap/avatar.scss
index ed6ec77b89b..0b147faf59e 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/avatar.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/avatar.scss
@@ -15,6 +15,7 @@
&.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; }
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/nav.scss b/app/assets/stylesheets/gitlab_bootstrap/nav.scss
index 0fc8b21de7b..847c7180ce2 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/nav.scss
+++ b/app/assets/stylesheets/gitlab_bootstrap/nav.scss
@@ -57,9 +57,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/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index a56c98cc5f1..129d33dcac3 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -1,4 +1,4 @@
-.black .highlight {
+.dark .highlight {
background-color: #333;
diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss
index 40f35b65da6..d057bcf669c 100644
--- a/app/assets/stylesheets/sections/events.scss
+++ b/app/assets/stylesheets/sections/events.scss
@@ -53,6 +53,14 @@
margin-top: 5px;
margin-left: 40px;
+ pre {
+ border: none;
+ background: #f9f9f9;
+ border-radius: 0;
+ color: #555;
+ margin: 0 20px;
+ }
+
.note-file-attach {
.note-image-attach {
margin-top: 4px;
diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/sections/graph.scss
index 58c2c203219..9be4cb788c1 100644
--- a/app/assets/stylesheets/sections/graph.scss
+++ b/app/assets/stylesheets/sections/graph.scss
@@ -11,7 +11,6 @@
.graph {
background: #f1f1f1;
- cursor: move;
height: 500px;
overflow-y: scroll;
overflow-x: hidden;
diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss
index e315b4ebcaa..38c08814224 100644
--- a/app/assets/stylesheets/sections/header.scss
+++ b/app/assets/stylesheets/sections/header.scss
@@ -77,6 +77,7 @@ header {
top: -4px;
img {
width: 26px;
+ height: 26px;
@include border-radius(4px);
}
}
diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss
index 5a1b476fe25..ed7902fec3a 100644
--- a/app/assets/stylesheets/sections/issues.scss
+++ b/app/assets/stylesheets/sections/issues.scss
@@ -106,3 +106,7 @@ input.check_all_issues {
#update_status {
width: 100px;
}
+
+.participants {
+ margin-bottom: 10px;
+}
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss
index a86384dc2ef..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;
@@ -85,12 +92,21 @@ ul.notes {
.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 {
@@ -309,3 +325,32 @@ ul.notes {
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/stat_graph.scss b/app/assets/stylesheets/sections/stat_graph.scss
new file mode 100644
index 00000000000..4baec343d6e
--- /dev/null
+++ b/app/assets/stylesheets/sections/stat_graph.scss
@@ -0,0 +1,48 @@
+.tint-box {
+ background: #f3f3f3;
+ position: relative;
+ margin-bottom: 10px;
+}
+
+.area {
+ fill: #1db34f;
+ fill-opacity: 0.5;
+}
+
+.axis {
+ fill: #aaa;
+ font-size: 10px;
+}
+
+#contributors .person {
+ &:nth-child(even) {
+ float: right;
+ }
+ float: left;
+ margin-top: 10px;
+}
+
+.contributors-list {
+ margin: 0 0 10px 0;
+ list-style: none;
+ padding: 0;
+}
+
+#contributors .person .spark {
+ display: block;
+ background: #f3f3f3;
+}
+
+#contributors .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/selects.scss b/app/assets/stylesheets/selects.scss
index 7abbe80bd39..9b7b6ad583c 100644
--- a/app/assets/stylesheets/selects.scss
+++ b/app/assets/stylesheets/selects.scss
@@ -10,6 +10,10 @@
.ajax-users-select {
width: 400px;
+
+ &.input-large {
+ width: 210px;
+ }
}
.user-result {
diff --git a/app/contexts/issues/list_context.rb b/app/contexts/issues/list_context.rb
index a35bddd6443..906a83b58a6 100644
--- a/app/contexts/issues/list_context.rb
+++ b/app/contexts/issues/list_context.rb
@@ -8,7 +8,7 @@ module Issues
@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.assigned(current_user)
+ when issues_filter[:to_me] then @project.issues.assigned_to(current_user)
when issues_filter[:by_me] then @project.issues.authored(current_user)
else @project.issues.opened
end
diff --git a/app/contexts/merge_requests_load_context.rb b/app/contexts/merge_requests_load_context.rb
index fde04085f26..fd44572c0eb 100644
--- a/app/contexts/merge_requests_load_context.rb
+++ b/app/contexts/merge_requests_load_context.rb
@@ -9,7 +9,7 @@ class MergeRequestsLoadContext < BaseContext
merge_requests = case type
when 'all' then merge_requests
when 'closed' then merge_requests.closed
- when 'assigned-to-me' then merge_requests.opened.assigned(current_user)
+ when 'assigned-to-me' then merge_requests.opened.assigned_to(current_user)
else merge_requests.opened
end
diff --git a/app/contexts/projects/create_context.rb b/app/contexts/projects/create_context.rb
index 2922564ba20..d3b8dee3948 100644
--- a/app/contexts/projects/create_context.rb
+++ b/app/contexts/projects/create_context.rb
@@ -51,6 +51,7 @@ module Projects
if shell.import_repository(@project.path_with_namespace, @project.import_url)
# We should create satellite for imported repo
@project.satellite.create unless @project.satellite.exists?
+ @project.imported = true
true
else
@project.errors.add(:import_url, 'cannot clone repo')
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 df520bea773..749c8fbecef 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
@@ -68,7 +62,8 @@ class Admin::GroupsController < Admin::ApplicationController
end
def project_teams_update
- @group.add_users_to_project_teams(params[:user_ids], params[:project_access])
+ @group.add_users_to_project_teams(params[:user_ids].split(','), params[:project_access])
+
redirect_to [:admin, @group], notice: 'Users were successfully added.'
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index bbb80cbb839..a63c4694a81 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -2,8 +2,10 @@ 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?
@@ -14,9 +16,6 @@ class Admin::ProjectsController < Admin::ApplicationController
def show
@repository = @project.repository
- @users = User.active
- @users = @users.not_in_project(@project) if @project.users.present?
- @users = @users.all
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/projects_controller.rb b/app/controllers/admin/teams/projects_controller.rb
index 8584a188b20..687f8f68bf2 100644
--- a/app/controllers/admin/teams/projects_controller.rb
+++ b/app/controllers/admin/teams/projects_controller.rb
@@ -12,7 +12,7 @@ class Admin::Teams::ProjectsController < Admin::Teams::ApplicationController
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.'
+ redirect_to admin_team_path(user_team), notice: 'Team of users was successfully assigned to projects.'
end
def edit
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 3075e7502f3..ec3209fdfe2 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -29,7 +29,7 @@ class Admin::UsersController < Admin::ApplicationController
def new
- @admin_user = User.new({ projects_limit: Gitlab.config.gitlab.default_projects_limit }, as: :admin)
+ @admin_user = User.new.with_defaults
end
def edit
@@ -55,8 +55,14 @@ class Admin::UsersController < Admin::ApplicationController
def create
admin = params[:user].delete("admin")
- @admin_user = User.new(params[:user], as: :admin)
+ opts = {
+ force_random_password: true,
+ password_expires_at: Time.now
+ }
+
+ @admin_user = User.new(params[:user].merge(opts), as: :admin)
@admin_user.admin = (admin && admin.to_i > 0)
+ @admin_user.created_by_id = current_user.id
respond_to do |format|
if @admin_user.save
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index d156166503f..fda05feefc0 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,7 +1,8 @@
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
@@ -32,7 +33,7 @@ class ApplicationController < ActionController::Base
def reject_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
@@ -40,16 +41,15 @@ class ApplicationController < ActionController::Base
def after_sign_in_path_for resource
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
@@ -154,7 +154,13 @@ class ApplicationController < ActionController::Base
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.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
+ 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
+ redirect_to new_profile_password_path and return
+ end
+ end
end
diff --git a/app/controllers/commit_controller.rb b/app/controllers/commit_controller.rb
index 1329410891d..a164de33107 100644
--- a/app/controllers/commit_controller.rb
+++ b/app/controllers/commit_controller.rb
@@ -11,7 +11,11 @@ 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]
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 2f418d40e78..b74c22b1547 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -68,7 +68,7 @@ class DashboardController < ApplicationController
end
def event_filter
- filters = cookies['event_filter'].split(',') if cookies['event_filter']
+ filters = cookies['event_filter'].split(',') if cookies['event_filter'].present?
@event_filter ||= EventFilter.new(filters)
end
end
diff --git a/app/controllers/deploy_keys_controller.rb b/app/controllers/deploy_keys_controller.rb
index c413c2fd1d9..35d28becd05 100644
--- a/app/controllers/deploy_keys_controller.rb
+++ b/app/controllers/deploy_keys_controller.rb
@@ -54,6 +54,6 @@ class DeployKeysController < ProjectResourceController
protected
def available_keys
- @available_keys ||= DeployKey.in_projects(current_user.owned_projects).uniq
+ @available_keys ||= current_user.accessible_deploy_keys
end
end
diff --git a/app/controllers/graphs_controller.rb b/app/controllers/graphs_controller.rb
new file mode 100644
index 00000000000..5ae9c15c0f7
--- /dev/null
+++ b/app/controllers/graphs_controller.rb
@@ -0,0 +1,17 @@
+class GraphsController < ProjectResourceController
+ # 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
+ @repo = @project.repository
+ @stats = Gitlab::Git::GitStats.new(@repo.raw, @repo.root_ref)
+ @log = @stats.parsed_log.to_json
+ end
+ end
+ end
+end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 8976262f4f7..e6559b8d8fe 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
@@ -72,7 +74,7 @@ class GroupsController < ApplicationController
end
def team_members
- @group.add_users_to_project_teams(params[:user_ids], params[:project_access])
+ @group.add_users_to_project_teams(params[:user_ids].split(','), params[:project_access])
redirect_to people_group_path(@group), notice: 'Users were successfully added.'
end
@@ -134,4 +136,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..2958367e0f8 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -1,4 +1,15 @@
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
end
diff --git a/app/controllers/graph_controller.rb b/app/controllers/network_controller.rb
index c79ed5ca3cc..3c8e747ba4e 100644
--- a/app/controllers/graph_controller.rb
+++ b/app/controllers/network_controller.rb
@@ -1,4 +1,4 @@
-class GraphController < ProjectResourceController
+class NetworkController < ProjectResourceController
include ExtractsPath
include ApplicationHelper
diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb
index 15ca963f281..dbec660b761 100644
--- a/app/controllers/notes_controller.rb
+++ b/app/controllers/notes_controller.rb
@@ -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
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
new file mode 100644
index 00000000000..0e5b42178a7
--- /dev/null
+++ b/app/controllers/passwords_controller.rb
@@ -0,0 +1,38 @@
+class 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 686edd8af80..6fa635d0e36 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -17,7 +17,7 @@ class ProfilesController < ApplicationController
end
def update
- if @user.update_attributes(user_attributes)
+ if @user.update_attributes(params[:user])
flash[:notice] = "Profile was successfully updated"
else
flash[:alert] = "Failed to update profile"
@@ -69,19 +69,6 @@ class ProfilesController < ApplicationController
@user = current_user
end
- def user_attributes
- user_attributes = params[:user]
-
- # Sanitize user input because we dont have strict
- # validation for this fields
- %w(name skype linkedin twitter bio).each do |attr|
- value = user_attributes[attr]
- user_attributes[attr] = sanitize(strip_tags(value)) if value.present?
- end
-
- user_attributes
- end
-
def authorize_change_password!
return render_404 if @user.ldap_user?
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 7e4776d2d75..86e4a7cbd6b 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -1,11 +1,4 @@
class Projects::ApplicationController < ApplicationController
-
- before_filter :authorize_admin_team_member!
-
- protected
-
- def user_team
- @team ||= UserTeam.find_by_path(params[:id])
- end
-
+ before_filter :project
+ before_filter :repository
end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
new file mode 100644
index 00000000000..1165fa1c583
--- /dev/null
+++ b/app/controllers/projects/snippets_controller.rb
@@ -0,0 +1,91 @@
+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]
+
+ layout 'project_resource'
+
+ 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/teams_controller.rb b/app/controllers/projects/teams_controller.rb
index 17e7367364a..c7d51b84fc4 100644
--- a/app/controllers/projects/teams_controller.rb
+++ b/app/controllers/projects/teams_controller.rb
@@ -1,5 +1,7 @@
class Projects::TeamsController < Projects::ApplicationController
+ before_filter :authorize_admin_team_member!
+
def available
@teams = current_user.is_admin? ? UserTeam.scoped : current_user.user_teams
@teams = @teams.without_project(project)
@@ -24,4 +26,9 @@ class Projects::TeamsController < Projects::ApplicationController
redirect_to project_team_index_path(project)
end
+ protected
+
+ def user_team
+ @team ||= UserTeam.find_by_path(params[:id])
+ end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index e202ed3234e..fad681eeef8 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -7,7 +7,8 @@ class ProjectsController < ProjectResourceController
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
@@ -80,14 +81,15 @@ class ProjectsController < ProjectResourceController
end
def fork
- @project = ::Projects::ForkContext.new(project, current_user).execute
+ @forked_project = ::Projects::ForkContext.new(project, current_user).execute
respond_to do |format|
format.html do
- if @project.saved? && @project.forked?
- redirect_to(@project, notice: 'Project was successfully forked.')
+ if @forked_project.saved? && @forked_project.forked?
+ redirect_to(@forked_project, notice: 'Project was successfully forked.')
else
- render action: "new"
+ @title = 'Fork project'
+ render action: "fork"
end
end
format.js
@@ -105,4 +107,10 @@ class ProjectsController < ProjectResourceController
format.json { render :json => @suggestions }
end
end
+
+ private
+
+ def set_title
+ @title = 'New Project'
+ end
end
diff --git a/app/controllers/refs_controller.rb b/app/controllers/refs_controller.rb
index e7def3984f8..cae9193a1da 100644
--- a/app/controllers/refs_controller.rb
+++ b/app/controllers/refs_controller.rb
@@ -14,7 +14,7 @@ class RefsController < ProjectResourceController
elsif params[:destination] == "blob"
project_blob_path(@project, (@id))
elsif params[:destination] == "graph"
- project_graph_path(@project, @id, @options)
+ project_network_path(@project, @id, @options)
else
project_commits_path(@project, @id)
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 507a5c206c6..194dfcd4122 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -16,8 +16,7 @@ class RegistrationsController < Devise::RegistrationsController
def build_resource(hash=nil)
super
- self.resource.projects_limit = Gitlab.config.gitlab.default_projects_limit
- self.resource
+ self.resource.with_defaults
end
private
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index a2e22a670a3..b91f68aab5e 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,38 +1,60 @@
-class SnippetsController < ProjectResourceController
- before_filter :module_enabled
+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
@@ -40,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
@@ -75,18 +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 module_enabled
- return render_404 unless @project.snippets_enabled
+ def set_title
+ @title = 'Snippets'
end
end
diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb
index 26c1d84f86b..57ab2a88e03 100644
--- a/app/controllers/teams_controller.rb
+++ b/app/controllers/teams_controller.rb
@@ -6,7 +6,9 @@ class TeamsController < ApplicationController
before_filter :user_team, except: [:new, :create]
- layout 'user_team', except: [:new, :create]
+ layout :determine_layout
+
+ before_filter :set_title, only: [:new, :create]
def show
projects
@@ -15,7 +17,7 @@ class TeamsController < ApplicationController
def edit
projects
- @avaliable_projects = current_user.admin? ? Project.without_team(user_team) : current_user.owned_projects.without_team(user_team)
+ @avaliable_projects = current_user.owned_projects.without_team(user_team)
end
def update
@@ -76,4 +78,16 @@ class TeamsController < ApplicationController
def user_team
@team ||= current_user.authorized_teams.find_by_path(params[:id])
end
+
+ def set_title
+ @title = 'New Team'
+ end
+
+ def determine_layout
+ if [:new, :create].include?(action_name.to_sym)
+ 'navless'
+ else
+ 'user_team'
+ end
+ 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/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 65d03fe0ccd..a73d574f22e 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
#
@@ -124,14 +142,7 @@ module ApplicationHelper
end
def user_color_scheme_class
- case current_user.color_scheme_id
- when 1 then 'white'
- when 2 then 'black'
- when 3 then 'solarized-dark'
- when 4 then 'monokai'
- else
- 'white'
- end
+ COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end
# Define whenever show last push event
@@ -181,9 +192,12 @@ module ApplicationHelper
alias_method :url_to_image, :image_url
def users_select_tag(id, opts = {})
- css_class = "ajax-users-select"
- css_class << " multiselect" if opts[:multiple]
- hidden_field_tag(id, '', class: css_class)
+ 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
@@ -202,4 +216,13 @@ module ApplicationHelper
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
+
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 7155036eeed..7c1aee76d4a 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -83,4 +83,40 @@ module EventsHelper
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_id}"
+ end
+ end
+ end
+ elsif event.wall_note?
+ link_to 'wall', project_wall_path(event.project)
+ else
+ content_tag :strong do
+ "(deleted)"
+ end
+ end
+ end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index dc5aa6e1fb6..d7d50db8a2f 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -15,7 +15,7 @@ module IssuesHelper
# 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 issues_filter
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index a9a6c78654f..69ad3de5031 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -1,13 +1,7 @@
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.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ]
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index fbd0f01e5d4..a3ec4cca59d 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -28,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/oauth_helper.rb b/app/helpers/oauth_helper.rb
index 017c22d9e1f..c0177dacbf8 100644
--- a/app/helpers/oauth_helper.rb
+++ b/app/helpers/oauth_helper.rb
@@ -10,4 +10,10 @@ module OauthHelper
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/projects_helper.rb b/app/helpers/projects_helper.rb
index 9b142714980..22aec62e2bf 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -17,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
@@ -25,10 +25,10 @@ module ProjectsHelper
author_html = ""
# Build avatar image tag
- author_html << image_tag(gravatar_icon(author.try(:email)), width: 16, class: "avatar avatar-inline s16") 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
@@ -48,4 +48,36 @@ module ProjectsHelper
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
+
+ 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
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 d2be4b1a7e6..19aba0f5f6d 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -73,7 +73,7 @@ module TabHelper
end
def project_tab_class
- return "active" if current_page?(controller: "projects", action: :edit, id: @project)
+ return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
if ['services', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name
"active"
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 1f764ea1038..af633f6fd04 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -48,7 +48,7 @@ module TreeHelper
end
def plain_text_readme? filename
- filename == 'README'
+ filename =~ /^README(.txt)?$/i
end
# Simple shortcut to File.join
@@ -85,4 +85,8 @@ module TreeHelper
file = File.join(tree.path, "..")
tree_join(tree.ref, file)
end
+
+ def leave_edit_message
+ "Leave edit mode?\nAll unsaved changes will be lost."
+ end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 8f0a6141b75..0b6314cd555 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -7,7 +7,8 @@ class Ability
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)
@@ -37,10 +38,14 @@ class Ability
elsif team.reporters.include?(user)
rules << project_report_rules
- elsif team.guests.include?(user) or project.public?
+ elsif team.guests.include?(user)
rules << project_guest_rules
end
+ if project.public?
+ rules << public_project_rules
+ end
+
if project.owner == user || user.admin?
rules << project_admin_rules
end
@@ -48,13 +53,30 @@ class Ability
rules.flatten
end
+ def public_project_rules
+ [
+ :download_code,
+ :fork_project,
+ :read_project,
+ :read_wiki,
+ :read_issue,
+ :read_milestone,
+ :read_project_snippet,
+ :read_team_member,
+ :read_merge_request,
+ :read_note,
+ :write_issue,
+ :write_note
+ ]
+ 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,8 +89,8 @@ class Ability
def project_report_rules
project_guest_rules + [
:download_code,
- :write_snippet,
- :fork_project
+ :fork_project,
+ :write_project_snippet
]
end
@@ -84,11 +106,11 @@ 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,
@@ -124,7 +146,7 @@ class Ability
rules = []
# Only group owner and administrators can manage team
- if team.owner == user || team.admin?(user) || user.admin?
+ if user.admin? || team.owner == user || team.admin?(user)
rules << [ :manage_user_team ]
end
@@ -135,8 +157,7 @@ class Ability
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/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 85337583640..38440859064 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -6,6 +6,7 @@
#
module Issuable
extend ActiveSupport::Concern
+ include Mentionable
included do
belongs_to :project
@@ -22,8 +23,10 @@ module Issuable
scope :closed, -> { with_state(:closed) }
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 :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") }
delegate :name,
:email,
@@ -95,4 +98,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..f22070f8504
--- /dev/null
+++ b/app/models/concerns/mentionable.rb
@@ -0,0 +1,37 @@
+# == Mentionable concern
+#
+# Contains common functionality shared between Issues and Notes
+#
+# Used by Issue, Note
+#
+module Mentionable
+ extend ActiveSupport::Concern
+
+ 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.users_projects.joins(:user).where(users: { username: identifier }).pluck(:user_id).first
+ else
+ id = User.where(username: identifier).pluck(:id).first
+ end
+ users << User.find(id) unless id.blank?
+ end
+ users.uniq
+ end
+
+ def mentionable_text
+ if self.class == Issue
+ description
+ elsif self.class == Note
+ note
+ else
+ nil
+ end
+ end
+
+end
diff --git a/app/models/event.rb b/app/models/event.rb
index 97f96ac8f9e..793ed445947 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -75,7 +75,9 @@ class Event < ActiveRecord::Base
end
def target_title
- target.try :title
+ if target && target.respond_to?(:title)
+ target.title
+ end
end
def push?
@@ -241,6 +243,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
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 91dd6477b04..de6e015c68e 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -27,7 +27,7 @@ class Issue < ActiveRecord::Base
scope :cared, ->(user) { where(assignee_id: user) }
scope :authored, ->(user) { where(author_id: user) }
- scope :open_for, ->(user) { opened.assigned(user) }
+ scope :open_for, ->(user) { opened.assigned_to(user) }
state_machine :state, initial: :opened do
event :close do
diff --git a/app/models/key.rb b/app/models/key.rb
index 185aef46e9e..61b4838f884 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -22,7 +22,7 @@ class Key < ActiveRecord::Base
before_validation :strip_white_space
validates :title, presence: true, length: { within: 0..255 }
- validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\Assh-.*\Z/ }, uniqueness: true
+ validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
validate :fingerprintable_key
delegate :name, :email, to: :user, prefix: true
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index b2ad1b76f1f..f41473336bc 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -91,6 +91,15 @@ class MergeRequest < ActiveRecord::Base
if target_branch == source_branch
errors.add :branch_conflict, "You can not use same branch for source and target branches"
end
+
+ if opened? || reopened?
+ similar_mrs = self.project.merge_requests.where(source_branch: source_branch, target_branch: target_branch).opened
+ similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
+
+ if similar_mrs.any?
+ errors.add :base, "There is already an open merge request for this branches"
+ end
+ end
end
def reload_code
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index cb7164eab13..c74e0cf5a1d 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -27,6 +27,7 @@ class Namespace < ActiveRecord::Base
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" }
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index ffec4712e45..da815a85924 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -25,7 +25,7 @@ module Network
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"]
+ h[item.commit_id] = item.note_count.to_i
end
h
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 7b7e6e99df4..56a8749e47d 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -19,6 +19,8 @@ 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
@@ -159,4 +161,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/personal_snippet.rb b/app/models/personal_snippet.rb
new file mode 100644
index 00000000000..d581c6092aa
--- /dev/null
+++ b/app/models/personal_snippet.rb
@@ -0,0 +1,18 @@
+# == Schema Information
+#
+# Table name: snippets
+#
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# author_id :integer not null
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# file_name :string(255)
+# expires_at :datetime
+# type :string(255)
+# private :boolean
+
+class PersonalSnippet < Snippet
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 9147aed3d40..e3f88b2cf98 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -57,7 +57,7 @@ class Project < ActiveRecord::Base
has_many :milestones, dependent: :destroy
has_many :users_projects, dependent: :destroy
has_many :notes, dependent: :destroy
- has_many :snippets, dependent: :destroy
+ has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :protected_branches, dependent: :destroy
has_many :user_team_project_relationships, dependent: :destroy
@@ -79,6 +79,7 @@ class Project < ActiveRecord::Base
format: { with: Gitlab::Regex.project_name_regex,
message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter 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" }
validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
@@ -89,10 +90,10 @@ class Project < ActiveRecord::Base
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
# Scopes
scope :without_user, ->(user) { where("projects.id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
@@ -110,11 +111,7 @@ class Project < ActiveRecord::Base
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
@@ -170,14 +167,6 @@ class Project < ActiveRecord::Base
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
@@ -424,6 +413,10 @@ class Project < ActiveRecord::Base
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
end
+ def imported?
+ imported
+ end
+
def rename_repo
old_path_with_namespace = File.join(namespace_dir, path_was)
new_path_with_namespace = File.join(namespace_dir, path)
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
new file mode 100644
index 00000000000..a86f2e7a32f
--- /dev/null
+++ b/app/models/project_snippet.rb
@@ -0,0 +1,27 @@
+# == Schema Information
+#
+# Table name: snippets
+#
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# author_id :integer not null
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# file_name :string(255)
+# expires_at :datetime
+# type :string(255)
+# private :boolean
+
+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/snippet.rb b/app/models/snippet.rb
index c4ee35e0556..1b37ffe8339 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -11,29 +11,31 @@
# updated_at :datetime not null
# file_name :string(255)
# expires_at :datetime
-#
+# type :string(255)
+# private :boolean
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/user.rb b/app/models/user.rb
index 255a5ebd2a9..ddbdec8acfc 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -42,8 +42,11 @@ class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
:skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password,
- :extern_uid, :provider, as: [:default, :admin]
- attr_accessible :projects_limit, :can_create_team, :can_create_group, as: :admin
+ :extern_uid, :provider, :password_expires_at,
+ as: [:default, :admin]
+
+ attr_accessible :projects_limit, :can_create_team, :can_create_group,
+ as: :admin
attr_accessor :force_random_password
@@ -78,6 +81,7 @@ class User < ActiveRecord::Base
has_many :team_projects, through: :user_team_project_relationships
# 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
@@ -89,6 +93,8 @@ class User < ActiveRecord::Base
has_many :personal_projects, through: :namespace, source: :projects
has_many :projects, through: :users_projects
+ has_many :master_projects, through: :users_projects, source: :project,
+ conditions: { users_projects: { project_access: UsersProject::MASTER } }
has_many :own_projects, foreign_key: :creator_id, class_name: 'Project'
has_many :owned_projects, through: :namespaces, source: :projects
@@ -101,6 +107,7 @@ class User < ActiveRecord::Base
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" }
@@ -109,7 +116,10 @@ class User < ActiveRecord::Base
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
@@ -196,6 +206,14 @@ class User < ActiveRecord::Base
username
end
+ def with_defaults
+ tap do |u|
+ u.projects_limit = Gitlab.config.gitlab.default_projects_limit
+ u.can_create_group = Gitlab.config.gitlab.default_can_create_group
+ u.can_create_team = Gitlab.config.gitlab.default_can_create_team
+ end
+ end
+
def notification
@notification ||= Notification.new(self)
end
@@ -236,8 +254,12 @@ class User < ActiveRecord::Base
end
def authorized_teams
- @team_ids ||= (user_teams.pluck(:id) + own_teams.pluck(:id)).uniq
- UserTeam.where(id: @team_ids)
+ if admin?
+ UserTeam.scoped
+ else
+ @team_ids ||= (user_teams.pluck(:id) + own_teams.pluck(:id)).uniq
+ UserTeam.where(id: @team_ids)
+ end
end
# Team membership in authorized projects
@@ -344,4 +366,19 @@ class User < ActiveRecord::Base
def ldap_user?
extern_uid && provider == 'ldap'
end
+
+ def accessible_deploy_keys
+ DeployKey.in_projects(self.master_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
end
diff --git a/app/models/user_team.rb b/app/models/user_team.rb
index 5de2ac6ae9e..a036cedc4c7 100644
--- a/app/models/user_team.rb
+++ b/app/models/user_team.rb
@@ -111,7 +111,6 @@ class UserTeam < ActiveRecord::Base
end
def admin?(member)
- user_team_user_relationships.with_user(member).first.group_admin?
+ user_team_user_relationships.with_user(member).first.try(:group_admin?)
end
-
end
diff --git a/app/observers/base_observer.rb b/app/observers/base_observer.rb
index 182d3b7b73c..92b73981d27 100644
--- a/app/observers/base_observer.rb
+++ b/app/observers/base_observer.rb
@@ -6,4 +6,8 @@ class BaseObserver < ActiveRecord::Observer
def log_info message
Gitlab::AppLogger.info message
end
+
+ def current_user
+ Thread.current[:current_user]
+ end
end
diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb
index 03ce4b95ac8..888fa7f6b73 100644
--- a/app/observers/issue_observer.rb
+++ b/app/observers/issue_observer.rb
@@ -1,6 +1,4 @@
class IssueObserver < BaseObserver
- cattr_accessor :current_user
-
def after_create(issue)
notification.new_issue(issue, current_user)
end
diff --git a/app/observers/merge_request_observer.rb b/app/observers/merge_request_observer.rb
index d0dfad8869d..03d4a22c1e6 100644
--- a/app/observers/merge_request_observer.rb
+++ b/app/observers/merge_request_observer.rb
@@ -1,6 +1,4 @@
class MergeRequestObserver < BaseObserver
- cattr_accessor :current_user
-
def after_create(merge_request)
notification.new_merge_request(merge_request, current_user)
end
diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb
index dda7be625da..3d4d161a1a2 100644
--- a/app/observers/project_observer.rb
+++ b/app/observers/project_observer.rb
@@ -1,13 +1,13 @@
class ProjectObserver < BaseObserver
def after_create(project)
- unless project.forked?
- GitlabShellWorker.perform_async(
- :add_repository,
- project.path_with_namespace
- )
-
- log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
- end
+ return true if project.forked? || project.imported?
+
+ GitlabShellWorker.perform_async(
+ :add_repository,
+ project.path_with_namespace
+ )
+
+ log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
end
def after_update(project)
@@ -15,6 +15,10 @@ class ProjectObserver < BaseObserver
project.rename_repo if project.path_changed?
end
+ def before_destroy(project)
+ project.repository.expire_cache unless project.empty_repo?
+ end
+
def after_destroy(project)
GitlabShellWorker.perform_async(
:remove_repository,
diff --git a/app/observers/user_observer.rb b/app/observers/user_observer.rb
index 6bb3c471d0c..e969405a598 100644
--- a/app/observers/user_observer.rb
+++ b/app/observers/user_observer.rb
@@ -10,12 +10,11 @@ class UserObserver < BaseObserver
end
def after_save user
- 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
+ # Ensure user has namespace
+ user.create_namespace!(path: user.username, name: user.username) unless user.namespace
+
+ if user.username_changed? || user.name_changed?
+ user.namespace.update_attributes(path: user.username, name: user.name)
end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 379d2c54629..b0243481b35 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -110,9 +110,11 @@ class NotificationService
else
opts.merge!(noteable_id: note.noteable_id)
target = note.noteable
- recipients = []
- recipients << target.assignee if target.respond_to?(:assignee)
- recipients << target.author if target.respond_to?(:author)
+ if target.respond_to?(:participants)
+ recipients = target.participants
+ else
+ recipients = []
+ end
end
# Get users who left comment in thread
@@ -181,7 +183,12 @@ class NotificationService
end
def new_resource_email(target, method)
- recipients = reject_muted_users([target.assignee], target.project)
+ if target.respond_to?(:participants)
+ recipients = target.participants
+ else
+ recipients = []
+ end
+ recipients = reject_muted_users(recipients, target.project)
recipients = recipients.concat(project_watchers(target.project)).uniq
recipients.delete(target.author)
diff --git a/app/views/admin/resque/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index 499738f9a06..499738f9a06 100644
--- a/app/views/admin/resque/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 46a876294ce..a69b26073f0 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -9,20 +9,20 @@
= link_to 'New Project', new_project_path, class: "btn btn-small"
.span4
.ui-box
- %h5.title Groups
- .data.padded
- = link_to admin_groups_path do
- %h1= Group.count
- %hr
- = link_to 'New Group', new_admin_group_path, class: "btn btn-small"
- .span4
- .ui-box
%h5.title Users
.data.padded
= link_to admin_users_path do
%h1= User.count
%hr
= link_to 'New User', new_admin_user_path, class: "btn btn-small"
+ .span4
+ .ui-box
+ %h5.title Groups
+ .data.padded
+ = link_to admin_groups_path do
+ %h1= Group.count
+ %hr
+ = link_to 'New Group', new_admin_group_path, class: "btn btn-small"
.row
.span4
@@ -50,6 +50,14 @@
%h4 Stats
%hr
%p
+ Teams
+ %span.light.pull-right
+ = UserTeam.count
+ %p
+ Forks
+ %span.light.pull-right
+ = ForkedProjectLink.count
+ %p
Issues
%span.light.pull-right
= Issue.count
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 0e2e144d326..9c4b91b1bfa 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -1,120 +1,93 @@
%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
- Edit
- %tr
- %td
- %b
- Description:
- %td
- = @group.description
- %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
+ %h5.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 Owned by:
+ %strong
+ - if @group.owner
+ = link_to @group.owner_name, admin_user_path(@group.owner)
+ - else
+ (deleted)
+ .pull-right
+ = link_to "#", class: "btn btn-small change-owner-link" do
+ %i.icon-edit
+ Change owner
+ %li.change-owner-holder.hide.bgred
+ .form-holder
+ %strong.cred New Owner:
+ = form_for [:admin, @group] do |f|
+ = users_select_tag(:"group[owner_id]")
+ .prepend-top-10
+ = f.submit 'Change Owner', class: "btn btn-remove"
+ = link_to "Cancel", "#", class: "btn change-owner-cancel-link"
- = 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:
+ %li
+ %span.light Created at:
+ %strong
+ = @group.created_at.stamp("March 1, 1999")
- - @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"}
- %tr
- %td= submit_tag 'Add user to projects in group', class: "btn btn-create"
- %td
+ .ui-box
+ %h5.title
+ Add user to Group projects:
+ .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_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 :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span2"}
+ %hr
+ = submit_tag 'Add user to projects in group', class: "btn btn-create"
+ .ui-box
+ %h5.title
+ Users from Group projects
+ %small
+ (#{@group.users.count})
+ %ul.well-list
+ - @group.users.sort_by(&:name).each do |user|
+ %li{class: dom_class(user)}
+ %strong
+ = link_to user.name, admin_user_path(user)
+ %span.pull-right.light
+ = pluralize user.authorized_projects.in_namespace(@group).count, 'project'
-= 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
+ .span6
+ .ui-box
+ %h5.title
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 'Move projects', class: "btn btn-create"
-
+ %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"
diff --git a/app/views/admin/hooks/_data_ex.html.erb b/app/views/admin/hooks/_data_ex.html.erb
index eeb78b5f0c5..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:
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 59831d83cc2..70ea460a06f 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -14,9 +14,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
@@ -47,9 +47,9 @@
- @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_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
diff --git a/app/views/admin/teams/index.html.haml b/app/views/admin/teams/index.html.haml
index cf24ed5398f..05ddc3cee89 100644
--- a/app/views/admin/teams/index.html.haml
+++ b/app/views/admin/teams/index.html.haml
@@ -1,43 +1,48 @@
%h3.page_title
- Teams
+ Teams (#{@teams.total_count})
%small
allow you to organize groups of people that have a common focus. Use teams to simplify the process of assigning roles to groups of people.
= link_to 'New Team', new_admin_team_path, class: "btn btn-small pull-right"
- %br
+%br
= form_tag admin_teams_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 Description
- %th Path
- %th Projects
- %th Members
- %th Owner
- %th.cred Danger Zone!
+%hr
+%ul.bordered-list
- @teams.each do |team|
- %tr
- %td
- %strong= link_to team.name, admin_team_path(team)
- %td= truncate team.description
- %td= team.path
- %td= team.projects.count
- %td= team.members.count
- %td
- - if team.owner
- = link_to team.owner.name, admin_user_path(team.owner)
- - else
- (deleted)
- %td.bgred
- = link_to 'Edit', 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"
+ %li
+ .clearfix
+ .pull-right.prepend-top-10
+ = link_to 'Edit', 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"
+
+ %h4
+ = link_to admin_team_path(team) do
+ %i.icon-group
+ = team.name
+
+ .clearfix.light.append-bottom-10
+ %span
+ %b Owner:
+ - if team.owner
+ = link_to team.owner.name, admin_user_path(team.owner)
+ - else
+ (deleted)
+ \|
+ %span
+ %b Users:
+ %span.badge= team.members.count
+ \|
+ %span
+ %b Projects:
+ %span.badge= team.projects.count
+
+ .clearfix
+ %p
+ = truncate team.description, length: 150
= paginate @teams, theme: "gitlab"
diff --git a/app/views/admin/teams/members/new.html.haml b/app/views/admin/teams/members/new.html.haml
index d3929cb7cdf..96b6d61b974 100644
--- a/app/views/admin/teams/members/new.html.haml
+++ b/app/views/admin/teams/members/new.html.haml
@@ -1,29 +1,27 @@
%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_username), 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
+ New members for
+ = link_to @team.name, admin_team_path(@team)
+ team
+%hr
+= form_tag admin_team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do
+ - if @team.errors.any?
+ .alert.alert-error
+ %span= @team.errors.full_messages.first
+ .clearfix
+ = label_tag :user_ids do
+ Users to add
+ .input
+ = select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_username), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
+ .clearfix.group-description-holder
+ = label_tag :default_project_access do
+ Default permission in projects
+ .input
+ = select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
+ .clearfix
+ = label_tag :group_admin do
+ Is team admin
+ .input
+ = check_box_tag :group_admin
+ .clearfix.form-actions
+ = submit_tag 'Add users into team', class: "btn btn-primary", id: :add_members_to_team
+ = link_to 'Cancel', :back, class: "btn"
diff --git a/app/views/admin/teams/projects/new.html.haml b/app/views/admin/teams/projects/new.html.haml
index b60dad35214..21bf65f9c3d 100644
--- a/app/views/admin/teams/projects/new.html.haml
+++ b/app/views/admin/teams/projects/new.html.haml
@@ -1,23 +1,18 @@
%h3.page_title
Team: #{@team.name}
+%hr
+= form_tag admin_team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do
+ %h6 Choose Projects you want to assign:
+ .clearfix
+ = label_tag :project_ids, "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'
-%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
+ %h6 Choose greatest user access for your team in these projects:
+ .clearfix
+ = label_tag :greatest_project_access, "Greatest Access"
+ .input
+ = select_tag :greatest_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
+
+ .form-actions
+ = submit_tag 'Add team to projects', class: "btn btn-create", id: :assign_projects_to_team
diff --git a/app/views/admin/teams/show.html.haml b/app/views/admin/teams/show.html.haml
index bd4d90b607f..cf105c096e3 100644
--- a/app/views/admin/teams/show.html.haml
+++ b/app/views/admin/teams/show.html.haml
@@ -1,93 +1,96 @@
%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
- Edit
- %tr
- %td
- %b
- Description:
- %td
- = @team.description
- %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"
+ = link_to edit_admin_team_path(@team), class: "btn btn-small pull-right" do
+ %i.icon-edit
+ Edit
+%hr
-%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
+
+.row
+ .span6
+ .ui-box
+ %h5.title
+ Team info:
+ %ul.well-list
+ %li
+ %span.light Name:
+ %strong= @team.name
+ %li
+ %span.light Path:
+ %strong
+ = @team.path
+
+ %li
+ %span.light Description:
+ %strong
+ = @team.description
+
+ %li
+ %span.light Owned by:
+ %strong
+ - if @team.owner
+ = link_to @team.owner.name, admin_user_path(@team.owner)
+ - else
+ (deleted)
+ .pull-right
+ = link_to "#", class: "btn btn-small change-owner-link" do
+ %i.icon-edit
+ Change owner
+ %li.change-owner-holder.hide.bgred
+ .form-holder
+ %strong.cred New Owner:
+ = form_for @team, url: admin_team_path(@team) do |f|
+ = users_select_tag(:"user_team[owner_id]")
+ .prepend-top-10
+ = f.submit 'Change Owner', class: "btn btn-remove"
+ = link_to "Cancel", "#", class: "btn change-owner-cancel-link"
+
+ %li
+ %span.light Created at:
+ %strong
+ = @team.created_at.stamp("March 1, 1999")
+
+ .span6
+ .ui-box
+ %h5.title
+ Members (#{@team.members.count})
+ .pull-right
+ = link_to 'Add members', new_admin_team_member_path(@team), class: "btn btn-small", id: :add_members_to_team
+ %ul.well-list#members_list
+ - @team.members.each do |member|
+ %li.member{ class: "user_#{member.id}"}
= 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}"
+ %strong
+ = member.name
+ .pull-right
+ %span.light
+ = @team.human_default_projects_access(member)
+ - if @team.admin?(member)
+ %span.label.label-info Admin
+ &nbsp;
+ = 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}"
+
+
+ .ui-box
+ %h5.title
+ Projects (#{@team.projects.count})
+ .pull-right
+ = link_to 'Add projects', new_admin_team_project_path(@team), class: "btn btn-small", id: :assign_projects_to_team
+ %ul.well-list#projects_list
+ - @team.projects.each do |project|
+ %li.project
+ = link_to [:admin, project] do
+ %strong
+ = project.name_with_namespace
-%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}"
+ .pull-right
+ %span.light
+ = @team.human_max_project_access(project)
+ &nbsp;
+ = 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}"
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index 9bde50f8947..fdf37965091 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -24,19 +24,25 @@
= 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?
+ - if @admin_user.new_record?
+ %fieldset
+ %legend Password
+ .clearfix
+ = f.label :password
+ .input
+ %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
+ .clearfix
+ = f.label :password
+ .input= f.password_field :password, disabled: f.object.force_random_password
.clearfix
- = f.label :force_random_password do
- %span Generate random password
- .input= f.check_box :force_random_password, {}, true, nil
+ = f.label :password_confirmation
+ .input= f.password_field :password_confirmation, disabled: f.object.force_random_password
%fieldset
%legend Access
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 6709b8f8a6b..3edd76a7c1b 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -1,32 +1,67 @@
+%h3.page_title
+ User:
+ = @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 grouped btn-small" do
+ %i.icon-edit
+ Edit
+ - unless @admin_user == current_user
+ - if @admin_user.blocked?
+ = link_to 'Unblock', unblock_admin_user_path(@admin_user), method: :put, class: "btn grouped btn-small success"
+ - else
+ = link_to 'Block', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn grouped btn-small btn-remove"
+ = link_to 'Destroy', [:admin, @admin_user], confirm: "USER #{@admin_user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn grouped btn-small btn-remove"
+%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
- %p
- %span.btn.btn-small
- %i.icon-envelope
- = mail_to @admin_user.email
- - unless @admin_user == current_user
- - if @admin_user.blocked?
- = link_to 'Unblock', unblock_admin_user_path(@admin_user), method: :put, class: "btn btn-small success"
- - else
- = link_to 'Block', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove"
- = link_to 'Destroy', [:admin, @admin_user], confirm: "USER #{@admin_user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-small btn-remove"
+ .ui-box
+ %h5.title
+ Account:
+ .pull-right
+ = image_tag gravatar_icon(@admin_user.email, 32), class:"avatar s32", alt:""
+ %ul.well-list
+ %li
+ %span.light Name:
+ %strong= @admin_user.name
+ %li
+ %span.light Username:
+ %strong
+ = @admin_user.username
+ %li
+ %span.light Email:
+ %strong
+ = mail_to @admin_user.email
+
+ %li
+ %span.light Member since:
+ %strong
+ = @admin_user.created_at.stamp("Nov 12, 2031")
+
+ %li
+ %span.light Last sign-in at:
+ %strong
+ - if @admin_user.last_sign_in_at
+ = @admin_user.last_sign_in_at.stamp("Nov 12, 2031")
+ - else
+ never
+
+ - if @admin_user.ldap_user?
+ %li
+ %span.light LDAP uid:
+ %strong
+ = @admin_user.extern_uid
+
+ - if @admin_user.created_by
+ %li
+ %span.light Created by:
+ %strong
+ = link_to @admin_user.created_by.name, [:admin, @admin_user.created_by]
%hr
%h5
Add User to Projects
@@ -67,11 +102,11 @@
.span6
- = render 'users/profile', user: @admin_user
.ui-box
%h5.title Projects (#{@projects.count})
%ul.well-list
- @projects.sort_by(&:name_with_namespace).each do |project|
+ - tm = project.team.get_tm(@admin_user.id)
%li
= link_to admin_project_path(project), class: dom_class(project) do
- if project.namespace
@@ -79,16 +114,17 @@
\/
%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
+
+ - if project.owner == @admin_user
+ %span.label.label-info owner
+
+ - if tm
+ .pull-right
+ = link_to edit_admin_project_member_path(project, tm.user), class: "btn grouped btn-small" do
%i.icon-edit
- = link_to admin_project_member_path(project, tm.user), confirm: remove_from_project_team_message(project, @admin_user), method: :delete, class: "btn btn-small btn-remove" do
+ = link_to admin_project_member_path(project, tm.user), confirm: remove_from_project_team_message(project, @admin_user), method: :delete, class: "btn grouped btn-small btn-remove" do
%i.icon-remove
- %p.light
- %i.icon-wrench
- &ndash; user is a project owner
+
+ .pull-right.light
+ = tm.project_access_human
+ &nbsp;
diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml
index 2a0514639d5..25239105e3b 100644
--- a/app/views/dashboard/projects.html.haml
+++ b/app/views/dashboard/projects.html.haml
@@ -20,22 +20,24 @@
= nav_tab :scope, 'joined' do
= link_to "Joined", projects_dashboard_path(scope: 'joined')
- %p.light Filter by label:
- %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
+ - if @labels.any?
+ %p.light Filter by label:
+ %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], { id: 'dashboard_projects_search', placeholder: 'Search', class: 'left input-xxlarge'}
- = button_tag type: 'submit', class: 'btn' do
- %i.icon-search
+ - if @projects.any?
+ = form_tag projects_dashboard_path, method: 'get' do
+ %fieldset.dashboard-search-filter
+ = hidden_field_tag "scope", params[:scope]
+ = search_field_tag "search", params[:search], { id: 'dashboard_projects_search', placeholder: 'Search', class: 'left input-xxlarge'}
+ = button_tag type: 'submit', class: 'btn' do
+ %i.icon-search
%ul.bordered-list
- @projects.each do |project|
@@ -45,7 +47,7 @@
= link_to project_path(project), class: dom_class(project) do
- if project.namespace
= project.namespace.human_name
- \/
+ %span= "/"
%strong
= truncate(project.name, length: 45)
.pull-right.light
diff --git a/app/views/deploy_keys/_deploy_key.html.haml b/app/views/deploy_keys/_deploy_key.html.haml
index 274015cb529..45f80ecd556 100644
--- a/app/views/deploy_keys/_deploy_key.html.haml
+++ b/app/views/deploy_keys/_deploy_key.html.haml
@@ -10,10 +10,10 @@
%i.icon-off
Disable
- else
- = link_to 'Remove', project_deploy_key_path(@project, deploy_key), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
+ = 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(@project, deploy_key) do
+ = 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
diff --git a/app/views/deploy_keys/index.html.haml b/app/views/deploy_keys/index.html.haml
index 9e136a6b6ba..7801302d3f4 100644
--- a/app/views/deploy_keys/index.html.haml
+++ b/app/views/deploy_keys/index.html.haml
@@ -29,4 +29,4 @@
= render @available_keys
- if @available_keys.blank?
.light-well
- %p.nothing_here_message All deploy keys from projects you can manage will be displayed here
+ %p.nothing_here_message All deploy keys created in projects you own will be displayed here
diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/sessions/_oauth_providers.html.haml
index 710a5d52514..935bc6af505 100644
--- a/app/views/devise/sessions/_oauth_providers.html.haml
+++ b/app/views/devise/sessions/_oauth_providers.html.haml
@@ -1,8 +1,9 @@
-- if enabled_oauth_providers.present?
+- providers = (enabled_oauth_providers - [:ldap])
+- if providers.present?
%hr
%div{:'data-no-turbolink' => 'data-no-turbolink'}
%span Sign in with: &nbsp;
- - (enabled_oauth_providers - [:ldap]).each do |provider|
+ - providers.each do |provider|
%span
- if default_providers.include?(provider)
= link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider)
diff --git a/app/views/edit_tree/show.html.haml b/app/views/edit_tree/show.html.haml
index 94a577d8e41..101b479afed 100644
--- a/app/views/edit_tree/show.html.haml
+++ b/app/views/edit_tree/show.html.haml
@@ -10,7 +10,7 @@
%strong= @ref
%span.options
.btn-group.tree-btn-group
- = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-tiny btn-cancel", confirm: "Are you sure?"
+ = 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
@@ -23,11 +23,11 @@
= 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'
+ = 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: "Are you sure?"
+ = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-cancel", confirm: leave_edit_message
:javascript
ace.config.set("modePath", "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict")
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index 2d80fc103f6..b50faf5a25c 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -1,6 +1,6 @@
%li.commit
%p
- = link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id"
+ = link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id", alt: ''
%span= commit[:author][:name]
&ndash;
= image_tag gravatar_icon(commit[:author][:email]), class: "avatar", width: 16
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index e2bf54ea5c9..80d16631fa0 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -4,7 +4,7 @@
#{time_ago_in_words(event.created_at)} ago.
= cache event do
- = image_tag gravatar_icon(event.author_email), class: "avatar s24"
+ = image_tag gravatar_icon(event.author_email), class: "avatar s24", alt:''
- if event.push?
= render "events/event/push", event: event
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index 8bcfa95ff62..9dd0767e896 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -1,20 +1,6 @@
.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', project_wall_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
diff --git a/app/views/graphs/show.html.haml b/app/views/graphs/show.html.haml
new file mode 100644
index 00000000000..05bc1436e6d
--- /dev/null
+++ b/app/views/graphs/show.html.haml
@@ -0,0 +1,31 @@
+.loading-graph
+ %center
+ .loading
+ %h3.page_title Building repository graph. Please wait a moment.
+
+.stat-graph
+ .header.clearfix
+ .pull-right
+ %select
+ %option{:value => "commits"} Commits
+ %option{:value => "additions"} Additions
+ %option{:value => "deletions"} Deletions
+ %h3#date_header.page_title
+ %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() {
+ $(".loading-graph").hide();
+ $(".stat-graph").show();
+ },
+ dataType: "script"
+ });
diff --git a/app/views/graphs/show.js.haml b/app/views/graphs/show.js.haml
new file mode 100644
index 00000000000..b7c9b4113e9
--- /dev/null
+++ b/app/views/graphs/show.js.haml
@@ -0,0 +1,16 @@
+: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()
+ })
+
diff --git a/app/views/groups/_new_group_member.html.haml b/app/views/groups/_new_group_member.html.haml
index 9cdbea60370..78db1c23320 100644
--- a/app/views/groups/_new_group_member.html.haml
+++ b/app/views/groups/_new_group_member.html.haml
@@ -5,7 +5,7 @@
%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.alphabetically, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true})
+ .input= users_select_tag(:user_ids, multiple: true)
%h6 2. Set access level for them
.clearfix
diff --git a/app/views/groups/_new_member.html.haml b/app/views/groups/_new_member.html.haml
index b3424b01bcb..4a762ed39ed 100644
--- a/app/views/groups/_new_member.html.haml
+++ b/app/views/groups/_new_member.html.haml
@@ -5,7 +5,7 @@
%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})
+ .input= users_select_tag(:user_ids, multiple: true)
%h6 2. Set access level for them
.clearfix
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index eb4f324b358..aaedcc585c1 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -19,14 +19,14 @@
- @group.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, 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"
+ = 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
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index b395a8bc6a3..4aa58dedc4a 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -1,5 +1,3 @@
-%h3.page_title New Group
-%hr
= form_for @group do |f|
- if @group.errors.any?
.alert.alert-error
@@ -15,14 +13,16 @@
.input
= f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
+ .clearfix
+ .input
+ %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"
- .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 a group directory
- %li You will be able to move existing projects into group
diff --git a/app/views/groups/people.html.haml b/app/views/groups/people.html.haml
index 3e4eb082f56..44f623006eb 100644
--- a/app/views/groups/people.html.haml
+++ b/app/views/groups/people.html.haml
@@ -12,7 +12,7 @@
%ul.well-list
- @users.each do |user|
%li
- = image_tag gravatar_icon(user.email, 16), class: "avatar s16"
+ = image_tag gravatar_icon(user.email, 16), class: "avatar s16" , alt: ''
%strong= user.name
%span.cgray= user.email
- if @group.owner == user
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index 807ff5f39b4..edf03642d82 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -9,8 +9,8 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
@events.each do |event|
if event.proper?
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 1ce008f7e85..8afc4ab4a12 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -13,7 +13,7 @@
.loading.hide
.side.span4
- if @group.description.present?
- .description.well.well-small.light
+ .description-block
= @group.description
= render "projects", projects: @projects
.prepend-top-20
diff --git a/app/views/help/_api_layout.html.haml b/app/views/help/_api_layout.html.haml
new file mode 100644
index 00000000000..2f649a509a5
--- /dev/null
+++ b/app/views/help/_api_layout.html.haml
@@ -0,0 +1,13 @@
+.row
+ .span3
+ = link_to help_path, class: 'btn append-bottom-20 btn-small' do
+ %i.icon-angle-left
+ Back to help
+ %br
+ %ul.nav.nav-pills.nav-stacked
+ - %w(README projects project_snippets repositories deploy_keys users session issues milestones 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/api.html.haml b/app/views/help/api.html.haml
index 0c502ada8fa..b6ad5e14fc3 100644
--- a/app/views/help/api.html.haml
+++ b/app/views/help/api.html.haml
@@ -1,116 +1,15 @@
-= render layout: 'help/layout' do
- %h3.page_title API
+= render layout: 'help/api_layout' do
+ %h3.page_title
+ %span.light API
+ %span
+ \/
+ = @category.titleize
%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'
- %li
- = link_to "System Hooks", "#system_hooks", '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"))
-
- .tab-pane#system_hooks
- .file_holder
- .file_title
- %i.icon-file
- System Hooks
- .file_content.wiki
- = preserve do
- = markdown File.read(Rails.root.join("doc", "api", "system_hooks.md"))
+ .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 7bda5cb6027..80ddc05b503 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -26,7 +26,9 @@
on the top of this page
%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"
diff --git a/app/views/help/markdown.html.haml b/app/views/help/markdown.html.haml
index 92c1e49be49..b4ff88c7f93 100644
--- a/app/views/help/markdown.html.haml
+++ b/app/views/help/markdown.html.haml
@@ -2,126 +2,6 @@
%h3.page_title GitLab Flavored Markdown
%br
- .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..4748b296960 100644
--- a/app/views/help/permissions.html.haml
+++ b/app/views/help/permissions.html.haml
@@ -54,7 +54,6 @@
%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
@@ -62,5 +61,6 @@
%fieldset
%legend Owner
%ul
+ %li Switch public mode
%li Transfer project to another namespace
%li Remove project
diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml
index b32d9192afc..57460e623ab 100644
--- a/app/views/issues/_form.html.haml
+++ b/app/views/issues/_form.html.haml
@@ -19,7 +19,12 @@
= f.label :assignee_id do
%i.icon-user
Assign to
- .input= f.select(:assignee_id, @project.users.alphabetically.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'})
+ .input
+ .pull-left
+ = f.select(:assignee_id, @project.users.alphabetically.collect {|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
@@ -55,31 +60,36 @@
: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;
- }
- });
- });
+ $("#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("liszt:updated");
+ e.preventDefault();
+ });
diff --git a/app/views/issues/_issues.html.haml b/app/views/issues/_issues.html.haml
index cc8d8d9cae2..fb15effceb8 100644
--- a/app/views/issues/_issues.html.haml
+++ b/app/views/issues/_issues.html.haml
@@ -53,7 +53,7 @@
- @project.users.sort_by(&:name).each do |user|
%li
= link_to project_issues_with_filter_path(@project, assignee_id: user.id) do
- = image_tag gravatar_icon(user.email), class: "avatar s16"
+ = image_tag gravatar_icon(user.email), class: "avatar s16", alt: ''
= user.name
.dropdown.inline.prepend-left-10
@@ -63,7 +63,7 @@
- if @milestone.present?
%strong= @milestone.title
- elsif params[:milestone_id] == "0"
- Unspecified
+ None (backlog)
- else
Any
%b.caret
@@ -72,7 +72,7 @@
= link_to project_issues_with_filter_path(@project, milestone_id: nil) do
Any
= link_to project_issues_with_filter_path(@project, milestone_id: 0) do
- Unspecified
+ None (backlog)
- issues_active_milestones.each do |milestone|
%li
= link_to project_issues_with_filter_path(@project, milestone_id: milestone.id) do
diff --git a/app/views/issues/show.html.haml b/app/views/issues/show.html.haml
index 2e204b8240d..891a01f14c9 100644
--- a/app/views/issues/show.html.haml
+++ b/app/views/issues/show.html.haml
@@ -65,4 +65,9 @@
- 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 "notes/notes_with_form"
diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml
index 2ea6c3e46d9..8a3581cd318 100644
--- a/app/views/layouts/_head_panel.html.haml
+++ b/app/views/layouts/_head_panel.html.haml
@@ -18,6 +18,9 @@
%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
@@ -34,5 +37,4 @@
%i.icon-signout
%li
= link_to current_user, class: "profile-pic" do
- = image_tag gravatar_icon(current_user.email, 26)
-
+ = image_tag(gravatar_icon(current_user.email, 26), alt: '')
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index ca77c26e23d..e9ca29ea3be 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -14,6 +14,6 @@
= 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
+ = 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
index 2ac35050b64..cae24c3170d 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -13,8 +13,6 @@
= 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
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index ec3da964037..c01523c59cd 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -3,41 +3,48 @@
= link_to project_path(@project), title: "Project" do
%i.icon-home
- - unless @project.empty_repo?
- - 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
+ - if project_nav_tab? :files
+ = nav_link(controller: %w(tree blob blame)) 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)) 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.repo_exists? && @project.merge_requests_enabled
+ - 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.wiki_enabled
+ - if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
= link_to 'Wiki', project_wiki_path(@project, :home)
- - if @project.wall_enabled
+ - if project_nav_tab? :wall
= nav_link(controller: :walls) do
= link_to 'Wall', project_wall_path(@project)
- - if @project.snippets_enabled
+ - if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
= link_to 'Snippets', project_snippets_path(@project)
- - if can? current_user, :admin_project, @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/nav/_team.html.haml b/app/views/layouts/nav/_team.html.haml
index 415e45104df..575c5b7e1f3 100644
--- a/app/views/layouts/nav/_team.html.haml
+++ b/app/views/layouts/nav/_team.html.haml
@@ -18,7 +18,7 @@
Members
%span.count= @team.members.count
- - if can? current_user, :admin_user_team, @team
+ - if can? current_user, :manage_user_team, @team
= nav_link(path: 'teams#edit') do
= link_to edit_team_path(@team), 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/public.html.haml b/app/views/layouts/public.html.haml
index 435250b6825..e330da9bd73 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -1,17 +1,20 @@
!!! 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
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ - if current_user
+ = render "layouts/head_panel", title: "Public Projects"
+ - else
+ %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.navless-container
.content
- .prepend-top-20
- = yield
+ = 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/milestones/_issues.html.haml b/app/views/milestones/_issues.html.haml
new file mode 100644
index 00000000000..eccf3ddbfa5
--- /dev/null
+++ b/app/views/milestones/_issues.html.haml
@@ -0,0 +1,11 @@
+.ui-box
+ %h5.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.id}
+ = link_to_gfm truncate(issue.title, length: 60), [@project, issue]
+ - if issue.assignee
+ .pull-right
+ = image_tag gravatar_icon(issue.assignee.email, 16), class: "avatar s16"
diff --git a/app/views/milestones/_merge_request.html.haml b/app/views/milestones/_merge_request.html.haml
new file mode 100644
index 00000000000..7f815894069
--- /dev/null
+++ b/app/views/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/show.html.haml b/app/views/milestones/show.html.haml
index 034c37852f1..de33ab363e6 100644
--- a/app/views/milestones/show.html.haml
+++ b/app/views/milestones/show.html.haml
@@ -55,39 +55,52 @@
= 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]
+%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
- .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? || merge_request.merged?}}
- = 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_member(@project, user)
+.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
+ %h5.title Open
+ %ul.well-list
+ - @merge_requests.opened.each do |merge_request|
+ = render 'merge_request', merge_request: merge_request
+ .span6
+ .ui-box
+ %h5.title Closed
+ %ul.well-list
+ - @merge_requests.closed.each do |merge_request|
+ = render 'merge_request', merge_request: merge_request
-.clearfix
+ .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/graph/_head.html.haml b/app/views/network/_head.html.haml
index 7a5b3c6f43d..62ab8b049ac 100644
--- a/app/views/graph/_head.html.haml
+++ b/app/views/network/_head.html.haml
@@ -5,7 +5,7 @@
.pull-left
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
.pull-left
- = form_tag project_graph_path(@project, @id), method: :get do |f|
+ = form_tag project_network_path(@project, @id), method: :get do |f|
.control-group
= label_tag :filter_ref, "Show only selected ref", class: 'control-label light'
.controls
@@ -14,7 +14,7 @@
= hidden_field_tag(key, value, id: nil) unless key == "filter_ref"
.search.pull-right
- = form_tag project_graph_path(@project, @id), method: :get do |f|
+ = form_tag project_network_path(@project, @id), method: :get do |f|
.control-group
= label_tag :search , "Looking for commit:", class: 'control-label light'
.controls
diff --git a/app/views/graph/show.html.haml b/app/views/network/show.html.haml
index 0ee6648317c..a480ceaf995 100644
--- a/app/views/graph/show.html.haml
+++ b/app/views/network/show.html.haml
@@ -11,7 +11,7 @@
$(this).closest('form').submit();
});
branch_graph = new BranchGraph($("#holder"), {
- url: '#{project_graph_path(@project, @ref, @options.merge(format: :json))}',
+ 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/graph/show.json.erb b/app/views/network/show.json.erb
index 9a62cdb3dc9..9a62cdb3dc9 100644
--- a/app/views/graph/show.json.erb
+++ b/app/views/network/show.json.erb
diff --git a/app/views/notes/_discussion.html.haml b/app/views/notes/_discussion.html.haml
index 24cb4228174..e65a4f390e6 100644
--- a/app/views/notes/_discussion.html.haml
+++ b/app/views/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", alt: ''
%div
= link_to_member(@project, note.author, avatar: false)
- if note.for_merge_request?
diff --git a/app/views/notes/_note.html.haml b/app/views/notes/_note.html.haml
index b355e2a0bd4..6089b9505b2 100644
--- a/app/views/notes/_note.html.haml
+++ b/app/views/notes/_note.html.haml
@@ -6,13 +6,14 @@
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
+ = link_to "#", title: "Edit comment", class: "js-note-edit" do
+ %i.icon-edit
+ = 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
- = image_tag gravatar_icon(note.author.email), class: "avatar s32"
+ = image_tag gravatar_icon(note.author.email), class: "avatar s32", alt: ''
= link_to_member(@project, note.author, avatar: false)
%span.note-last-update
- = time_ago_in_words(note.updated_at)
- ago
+ = note_timestamp(note)
- if note.upvote?
%span.vote.upvote.label.label-success
@@ -25,13 +26,37 @@
.note-body
- = preserve do
- = markdown(note.note)
+ .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
- - if note.attachment.image?
- = 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
- .clear
+ .note-attachment
+ - if note.attachment.image?
+ = 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 \ No newline at end of file
diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml
index 4ad3f201495..fc2c02ef827 100644
--- a/app/views/notify/new_user_email.html.haml
+++ b/app/views/notify/new_user_email.html.haml
@@ -8,9 +8,14 @@
%p
login..........................................
%code= @user['email']
-%p
- - unless Gitlab.config.gitlab.signup_enabled
+
+- 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
index 181ed2d2a49..70fe0e0736a 100644
--- a/app/views/notify/new_user_email.text.erb
+++ b/app/views/notify/new_user_email.text.erb
@@ -3,8 +3,11 @@ Hi <%= @user.name %>!
The Administrator created an account for you. Now you are a member of company GitLab application.
login.................. <%= @user.email %>
-<% unless Gitlab.config.gitlab.signup_enabled %>
+<% 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/passwords/new.html.haml b/app/views/passwords/new.html.haml
new file mode 100644
index 00000000000..c92424160b3
--- /dev/null
+++ b/app/views/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
+
+ .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 'Set new password', class: "btn btn-create"
diff --git a/app/views/profiles/account.html.haml b/app/views/profiles/account.html.haml
index 9bba73a080a..9b1354d9f2c 100644
--- a/app/views/profiles/account.html.haml
+++ b/app/views/profiles/account.html.haml
@@ -3,8 +3,8 @@
%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|
+ %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)
diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml
index 878297fe49d..9ada69c9d5d 100644
--- a/app/views/profiles/design.html.haml
+++ b/app/views/profiles/design.html.haml
@@ -40,23 +40,9 @@
%i.icon-ok
Saved
.code_highlight_opts
- = label_tag do
- .prev
- = image_tag "white.png"
- = f.radio_button :color_scheme_id, 1
- White
- = label_tag do
- .prev
- = image_tag "dark.png"
- = f.radio_button :color_scheme_id, 2
- Dark
- = label_tag do
- .prev
- = image_tag "solarized_dark.png"
- = f.radio_button :color_scheme_id, 3
- Solarized Dark
- = label_tag do
- .prev
- = image_tag "monokai.png"
- = f.radio_button :color_scheme_id, 4
- Monokai
+ - 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/show.html.haml b/app/views/profiles/show.html.haml
index d4793da8987..96870924371 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,5 +1,5 @@
.profile_avatar_holder
- = image_tag gravatar_icon(@user.email, 90)
+ = image_tag gravatar_icon(@user.email, 90), alt: ''
%h3.page_title
= @user.name
%br
diff --git a/app/views/projects/_clone_panel.html.haml b/app/views/projects/_clone_panel.html.haml
index f8276c3c2b6..ebce06edf74 100644
--- a/app/views/projects/_clone_panel.html.haml
+++ b/app/views/projects/_clone_panel.html.haml
@@ -7,11 +7,12 @@
- unless @project.empty_repo?
- if 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 btn-primary' do
+ = 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-copy
+ %i.icon-code-fork
Fork
- if can? current_user, :download_code, @project
= link_to archive_project_repository_path(@project), class: "btn grouped" do
diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml
index acaa03f9ad2..346bbd2daf3 100644
--- a/app/views/projects/_settings_nav.html.haml
+++ b/app/views/projects/_settings_nav.html.haml
@@ -5,8 +5,8 @@
Edit
= nav_link(controller: [:team_members, :teams]) do
= link_to project_team_index_path(@project), class: "team-tab tab" do
- %i.icon-user
- Team
+ %i.icon-group
+ Project Members
= nav_link(controller: :deploy_keys) do
= link_to project_deploy_keys_path(@project) do
%span
@@ -14,7 +14,7 @@
= nav_link(controller: :hooks) do
= link_to project_hooks_path(@project) do
%span
- Hooks
+ Web Hooks
= nav_link(controller: :services) do
= link_to project_services_path(@project) do
%span
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/new.html.haml b/app/views/projects/new.html.haml
index e9099f264bc..0754ee83114 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,6 +1,4 @@
.project-edit-container
- %h3.page_title New Project
- %hr
.project-edit-errors
= render 'projects/errors'
.project-edit-content
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index b2e9fd8431a..7d708ce7b8f 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -3,7 +3,7 @@
.row
.span9
= render "events/event_last_push", event: @last_push
- .content_list= render @events
+ .content_list
.loading.hide
.span3
.light-well
@@ -42,6 +42,7 @@
%p Owner: #{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)
diff --git a/app/views/projects/snippets/_blob.html.haml b/app/views/projects/snippets/_blob.html.haml
new file mode 100644
index 00000000000..e0d1669acbe
--- /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..99a8761daef
--- /dev/null
+++ b/app/views/projects/snippets/_form.html.haml
@@ -0,0 +1,41 @@
+%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
+
+ .clearfix
+ = 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
+ .file-editor
+ = f.label :file_name, "File"
+ .input
+ .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
+ = f.submit 'Save', class: "btn-save btn"
+ = link_to "Cancel", project_snippets_path(@project), class: " btn"
+ - unless @snippet.new_record?
+ .pull-right= link_to 'Destroy', project_snippet_path(@project, @snippet), confirm: 'Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", 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..72865bf2872
--- /dev/null
+++ b/app/views/projects/snippets/_snippet.html.haml
@@ -0,0 +1,25 @@
+%li
+ .snippet-title
+ - if snippet.private?
+ %i.icon-lock.cgreen
+ - else
+ %i.icon-globe.cblue
+ = link_to reliable_snippet_path(snippet) do
+ %h5.inline
+ = truncate(snippet.title, length: 60)
+ %span.cgray
+ = snippet.file_name
+
+ %small.pull-right.cgray
+ Expires:
+ - if snippet.expires_at
+ = snippet.expires_at.to_date.to_s(:short)
+ - else
+ Never
+
+ .snippet-info.prepend-left-20
+ = "##{snippet.id}"
+ %span.light
+ by
+ = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16"
+ = snippet.author_name
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..ce36bed1e0d
--- /dev/null
+++ b/app/views/projects/snippets/index.html.haml
@@ -0,0 +1,13 @@
+%h3.page_title
+ Snippets
+ %small share code pastes with others out of git repository
+
+ - if can? current_user, :write_project_snippet, @project
+ = link_to new_project_snippet_path(@project), class: "btn btn-small add_new pull-right", title: "New Snippet" do
+ Add new snippet
+%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..da93e4bdbb1
--- /dev/null
+++ b/app/views/projects/snippets/show.html.haml
@@ -0,0 +1,13 @@
+%h3.page_title
+ %i.icon-lock.cgreen
+ = @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
+%br
+%div= render 'projects/snippets/blob'
+%div#notes= render "notes/notes_with_form"
diff --git a/app/views/projects/teams/available.html.haml b/app/views/projects/teams/available.html.haml
index 29fe8ed25cd..880a02aeaf5 100644
--- a/app/views/projects/teams/available.html.haml
+++ b/app/views/projects/teams/available.html.haml
@@ -10,7 +10,7 @@
.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:
+ %p.slead Choose greatest user access for your team in this project:
.padded
= label_tag :team_ids, "Permission"
.input= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles), {class: "project-access-select chosen span3" }
diff --git a/app/views/protected_branches/index.html.haml b/app/views/protected_branches/index.html.haml
index 15644de552f..a338344c52d 100644
--- a/app/views/protected_branches/index.html.haml
+++ b/app/views/protected_branches/index.html.haml
@@ -51,4 +51,4 @@
(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"
+ = link_to 'Unprotect', [@project, branch], confirm: 'Branch will be writable for developers. Are you sure?', method: :delete, class: "btn btn-remove btn-small"
diff --git a/app/views/repositories/_branch.html.haml b/app/views/repositories/_branch.html.haml
index dd91e14b66b..2115f3c427f 100644
--- a/app/views/repositories/_branch.html.haml
+++ b/app/views/repositories/_branch.html.haml
@@ -12,7 +12,7 @@
%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"
+ = image_tag gravatar_icon(commit.author_email), class: "avatar s16", alt: ''
%span.light
= gfm escape_once(truncate(commit.title, length: 40))
%span
diff --git a/app/views/repositories/_feed.html.haml b/app/views/repositories/_feed.html.haml
index 6bb75265ffb..faa3ed1746c 100644
--- a/app/views/repositories/_feed.html.haml
+++ b/app/views/repositories/_feed.html.haml
@@ -11,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/repositories/stats.html.haml
index 6d1fb4686ea..fc8ccfe458d 100644
--- a/app/views/repositories/stats.html.haml
+++ b/app/views/repositories/stats.html.haml
@@ -19,7 +19,7 @@
%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
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 62e0fa25898..5120902fa0e 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -4,10 +4,8 @@
= text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span7", readonly: true
%span.add-on
- if @project.public
- .cblue
- %i.icon-share
- public
+ = public_icon
+ %span.cblue public
- else
- .cgreen
- %i.icon-lock
- private
+ = private_icon
+ %span.cgreen private
diff --git a/app/views/snippets/_blob.html.haml b/app/views/snippets/_blob.html.haml
index 017a33b34f3..c538da0bee5 100644
--- a/app/views/snippets/_blob.html.haml
+++ b/app/views/snippets/_blob.html.haml
@@ -3,7 +3,11 @@
%i.icon-file
%strong= @snippet.file_name
%span.options
- = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank"
+ .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}
diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml
index 77162cdcde3..95e9e0357bc 100644
--- a/app/views/snippets/_form.html.haml
+++ b/app/views/snippets/_form.html.haml
@@ -2,7 +2,7 @@
= @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
@@ -13,6 +13,9 @@
= f.label :title
.input= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true
.clearfix
+ = f.label "Private?"
+ .input= f.check_box :private, {class: ''}
+ .clearfix
= f.label "Lifetime"
.input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'}
.clearfix
@@ -28,9 +31,9 @@
.form-actions
= f.submit 'Save', class: "btn-save btn"
- = link_to "Cancel", project_snippets_path(@project), class: " btn"
+ = link_to "Cancel", snippets_path(@project), class: " btn"
- 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..a013cdfe16a 100644
--- a/app/views/snippets/_snippet.html.haml
+++ b/app/views/snippets/_snippet.html.haml
@@ -1,13 +1,30 @@
-%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
+%li
+ .snippet-title
+ - if snippet.private?
+ = private_icon
+ - else
+ = public_icon
+ = link_to reliable_snippet_path(snippet) do
+ %h5.inline
+ = truncate(snippet.title, length: 60)
%span.cgray
- - if snippet.expires_at
- = snippet.expires_at.to_date.to_s(:short)
- - else
- Never
+ = snippet.file_name
+
+ %small.pull-right.cgray
+ - if snippet.project_id?
+ = link_to snippet.project.name_with_namespace, project_path(snippet.project)
+ %span
+ \|
+ Expires:
+ - if snippet.expires_at
+ = snippet.expires_at.to_date.to_s(:short)
+ - else
+ Never
+
+ .snippet-info.prepend-left-20
+ = "##{snippet.id}"
+ %span.light
+ by
+ = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
+ = snippet.author_name
+
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
new file mode 100644
index 00000000000..636bf37f6d9
--- /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
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..cf5c3084dc4
--- /dev/null
+++ b/app/views/snippets/current_user_index.html.haml
@@ -0,0 +1,24 @@
+%h3.page_title
+ My Snippets
+ %small share code pastes with others out of git repository
+ .pull-right
+ = link_to new_snippet_path, class: "btn btn-small add_new grouped btn-primary", title: "New Snippet" do
+ Add new snippet
+ = link_to snippets_path, class: "btn btn-small grouped" do
+ Discover snippets
+
+%hr
+
+.row
+ .span3
+ %ul.nav.nav-pills.nav-stacked
+ = nav_tab :scope, nil do
+ = link_to "All", user_snippets_path(@user)
+ = nav_tab :scope, 'private' do
+ = link_to "Private", user_snippets_path(@user, scope: 'private')
+ = nav_tab :scope, 'public' do
+ = link_to "Public", user_snippets_path(@user, scope: 'public')
+
+ .span9
+ = render 'snippets'
+
diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml
index f81c0b8bc64..1b88a85faf1 100644
--- a/app/views/snippets/edit.html.haml
+++ b/app/views/snippets/edit.html.haml
@@ -1 +1 @@
-= 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 bacf23d8f8d..4301f90f9d6 100644
--- a/app/views/snippets/index.html.haml
+++ b/app/views/snippets/index.html.haml
@@ -1,19 +1,15 @@
%h3.page_title
- Snippets
+ Public 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-small add_new grouped btn-primary", 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 btn-small grouped" do
+ My snippets
+
+%hr
+.row
+ .span12
+ = render 'snippets'
+
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index f81c0b8bc64..90e0a1f79da 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -1 +1 @@
-= 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 12534edf8ba..ac6daed56b6 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -1,9 +1,16 @@
%h3.page_title
+ - if @snippet.private?
+ %i{:class => "icon-lock cgreen has_bottom_tooltip", "data-original-title" => "Private snippet"}
+ - else
+ %i{:class => "icon-globe cblue has_bottom_tooltip", "data-original-title" => "Public snippet"}
+
= @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", title: 'Edit Snippet'
+ %small.pull-right
+ = "##{@snippet.id}"
+ %span.light
+ by
+ = image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
+ = @snippet.author_name
%br
%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..79e3ddf96fd
--- /dev/null
+++ b/app/views/snippets/user_index.html.haml
@@ -0,0 +1,13 @@
+%h3.page_title
+ = image_tag gravatar_icon(@user.email), class: "avatar s24"
+ = @user.name
+ %span
+ \/
+ Snippets
+ %small share code pastes with others out of git repository
+ = 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/_team.html.haml b/app/views/team_members/_team.html.haml
index 4b49b308edc..4ff170ac86e 100644
--- a/app/views/team_members/_team.html.haml
+++ b/app/views/team_members/_team.html.haml
@@ -1,3 +1,4 @@
+- can_admin_project = (can? current_user, :admin_project, @project)
- team.each do |access, members|
- role = Project.access_options.key(access).pluralize
.ui-box{class: role.downcase}
@@ -6,4 +7,4 @@
%span.light (#{members.size})
%ul.well-list
- members.sort_by(&:user_name).each do |team_member|
- = render 'team_members/team_member', member: team_member
+ = render 'team_members/team_member', member: team_member, current_user_can_admin_project: can_admin_project
diff --git a/app/views/team_members/_team_member.html.haml b/app/views/team_members/_team_member.html.haml
index 5fd8d2465d1..d61513fa03a 100644
--- a/app/views/team_members/_team_member.html.haml
+++ b/app/views/team_members/_team_member.html.haml
@@ -1,10 +1,10 @@
- user = member.user
-- allow_admin = can? current_user, :admin_project, @project
+- allow_admin = current_user_can_admin_project
%li{id: dom_id(user), class: "team_member_row user_#{user.id}"}
.row
.span4
= link_to user, title: user.name, class: "dark" do
- = image_tag gravatar_icon(user.email, 32), class: "avatar s32"
+ = image_tag gravatar_icon(user.email, 32), class: "avatar s32", alt: ''
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
diff --git a/app/views/team_members/index.html.haml b/app/views/team_members/index.html.haml
index 3132fd5ca8a..c0f7ee4330d 100644
--- a/app/views/team_members/index.html.haml
+++ b/app/views/team_members/index.html.haml
@@ -1,6 +1,6 @@
= render "projects/settings_nav"
%h3.page_title
- Team Members
+ Project Members
(#{@project.users.count})
%small
Read more about project permissions
diff --git a/app/views/teams/edit.html.haml b/app/views/teams/edit.html.haml
index c9d573ea7e4..7f2273ee26c 100644
--- a/app/views/teams/edit.html.haml
+++ b/app/views/teams/edit.html.haml
@@ -5,8 +5,9 @@
= link_to 'Projects', '#tab-projects', 'data-toggle' => 'tab'
%li
= link_to 'Edit Team', '#tab-edit', 'data-toggle' => 'tab'
- %li
- = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab'
+ - if can? current_user, :admin_user_team, @team
+ %li
+ = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab'
.span9
.tab-content
diff --git a/app/views/teams/members/_show.html.haml b/app/views/teams/members/_member.html.haml
index dc32acb4693..19591db03e2 100644
--- a/app/views/teams/members/_show.html.haml
+++ b/app/views/teams/members/_member.html.haml
@@ -2,30 +2,30 @@
- allow_admin = can? current_user, :manage_user_team, @team
%li{id: dom_id(member), class: "team_member_row user_#{user.id}"}
.row
- .span4
+ .span3
= link_to user_path(user.username), title: user.name, class: "dark" do
- = image_tag gravatar_icon(user.email, 40), class: "avatar s32"
+ = image_tag gravatar_icon(user.email, 40), class: "avatar s32", alt: ''
= link_to user_path(user.username), title: user.name, class: "dark" do
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
- .span7.pull-right
+ .span5.pull-right
- if allow_admin
.pull-left
= form_for(member, as: :team_member, url: team_member_path(@team, user)) do |f|
- = label_tag do
+ = label_tag :group_admin do
= f.check_box :group_admin, class: 'trigger-submit'
%span Admin access
&nbsp;
- = f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "medium trigger-submit"
+ = f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "span2 trigger-submit"
.pull-right
- if current_user == user
- %span.btn.disabled This is you!
+ %span.label.label-success This is you!
- if @team.owner == user
- %span.btn.disabled Owner
+ %span.label.label-info Owner
- elsif user.blocked?
- %span.btn.disabled.blocked Blocked
+ %span.label.label-error 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", title: "Remove from team" do
%i.icon-minus.icon-white
diff --git a/app/views/teams/members/_team.html.haml b/app/views/teams/members/_team.html.haml
index d8afc1fa371..52bb597cd80 100644
--- a/app/views/teams/members/_team.html.haml
+++ b/app/views/teams/members/_team.html.haml
@@ -1,16 +1,10 @@
-- grouped_user_team_members(@team).each do |access, members|
+- grouped_user_team_members(team).each do |access, members|
+ - access_key = Project.access_options.key(access)
+ - next if params[:type].present? && params[:type] != access_key.tableize
.ui-box
%h5.title
- = Project.access_options.key(access).pluralize
+ = access_key.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();
- });
- })
+ %ul.well-list.team-members
+ - members.sort_by(&:user_name).each do |member|
+ = render 'teams/members/member', member: member
diff --git a/app/views/teams/members/index.html.haml b/app/views/teams/members/index.html.haml
index 87438266cfb..02700e9fb71 100644
--- a/app/views/teams/members/index.html.haml
+++ b/app/views/teams/members/index.html.haml
@@ -12,6 +12,26 @@
%hr
-.clearfix
-%div.team-table
- = render partial: "teams/members/team", locals: {project: @team}
+.row
+ .span3
+ %ul.nav.nav-pills.nav-stacked
+ %li{class: ("active" if !params[:type])}
+ = link_to team_members_path(@team, type: nil) do
+ All
+ %li{class: ("active" if params[:type] == 'masters')}
+ = link_to team_members_path(@team, type: 'masters') do
+ Masters
+ %li{class: ("active" if params[:type] == 'developers')}
+ = link_to team_members_path(@team, type: 'developers') do
+ Developers
+ %li{class: ("active" if params[:type] == 'reporters')}
+ = link_to team_members_path(@team, type: 'reporters') do
+ Reporters
+ %li{class: ("active" if params[:type] == 'guests')}
+ = link_to team_members_path(@team, type: 'guests') do
+ Guests
+
+ .span9
+ .clearfix
+ %div.team-table
+ = render "teams/members/team", team: @team
diff --git a/app/views/teams/new.html.haml b/app/views/teams/new.html.haml
index 99d308217e0..fe1f9cf10ec 100644
--- a/app/views/teams/new.html.haml
+++ b/app/views/teams/new.html.haml
@@ -1,5 +1,3 @@
-%h3.page_title New Team
-%hr
= form_for @team, url: teams_path do |f|
- if @team.errors.any?
.alert.alert-error
@@ -15,16 +13,16 @@
.input
= f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
+
+ .clearfix
+ .input
+ %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
.form-actions
= f.submit 'Create team', class: "btn btn-create"
- .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
- %hr
-
- if current_user.can_create_group?
.clearfix
.input.light
diff --git a/app/views/teams/show.atom.builder b/app/views/teams/show.atom.builder
index 1468ac1d03c..fffb78d53e8 100644
--- a/app/views/teams/show.atom.builder
+++ b/app/views/teams/show.atom.builder
@@ -9,8 +9,8 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
@events.each do |event|
if event.proper?
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/teams/show.html.haml b/app/views/teams/show.html.haml
index 2ad7f743010..8582f85a2b5 100644
--- a/app/views/teams/show.html.haml
+++ b/app/views/teams/show.html.haml
@@ -12,7 +12,7 @@
.loading.hide
.side.span4
- if @team.description.present?
- .description.well.well-small.light
+ .description-block
= @team.description
= render "projects", projects: @projects
.prepend-top-20
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 10bd90b166a..548458e0d3b 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,7 +1,7 @@
.row
.span8
%h3.page_title
- = image_tag gravatar_icon(@user.email, 90), class: "avatar s90"
+ = image_tag gravatar_icon(@user.email, 90), class: "avatar s90", alt: ''
= @user.name
- if @user == current_user
.pull-right
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 4a4aa3465a6..4b74f3348f8 100644
--- a/config/database.yml.postgresql
+++ b/config/database.yml.postgresql
@@ -5,7 +5,7 @@ production:
adapter: postgresql
encoding: unicode
database: gitlabhq_production
- pool: 5
+ pool: 10
username: git
password:
# host: localhost
diff --git a/config/environments/production.rb b/config/environments/production.rb
index dc8e25593ae..7e02c75e562 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -52,7 +52,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)
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 413560159d5..ad24ba97c6f 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -1,4 +1,4 @@
-# # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # #
# GitLab application config file #
# # # # # # # # # # # # # # # # # #
#
@@ -18,6 +18,7 @@ production: &base
host: localhost
port: 80
https: false
+ # WARNING: This feature is no longer supported
# Uncomment and customize to run in non-root path
# Note that ENV['RAILS_RELATIVE_URL_ROOT'] in config/puma.rb may need to be changed
# relative_url_root: /gitlab
@@ -32,12 +33,14 @@ 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
+ # default_can_create_team: false # default: true
+ # username_changing_enabled: false # default: true - User can change her username/namespace
+
## Users management
# signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled.
- # username_changing_enabled: false # default: true - User can change her username/namespace
## Default project features settings
default_projects_features:
@@ -68,6 +71,11 @@ production: &base
# ## :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:
@@ -161,12 +169,12 @@ production: &base
# 4. Extra customization
# ==========================
- extra:
+ extra:
## Google analytics. Uncomment if you want it
# google_analytics_id: '_your_tracking_id'
## Text under sign-in page (Markdown enabled)
- # sign_in_text: |
+ # sign_in_text: |
# ![Company Logo](http://www.companydomain.com/logo.png)
# [Learn more about CompanyName](http://www.companydomain.com/)
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 27605efddfe..4afe50f1814 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -48,7 +48,9 @@ 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_can_create_team'] = true if Settings.gitlab['default_can_create_team'].nil?
Settings.gitlab['host'] ||= 'localhost'
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
@@ -58,6 +60,11 @@ 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['default_projects_features'] ||= {}
@@ -79,10 +86,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
@@ -111,3 +118,12 @@ Settings.satellites['path'] = File.expand_path(Settings.satellites['path'] || "t
# 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 27a0c0ffeb2..e2f98002347 100644
--- a/config/initializers/2_app.rb
+++ b/config/initializers/2_app.rb
@@ -6,3 +6,8 @@ module Gitlab
Settings
end
end
+
+#
+# Load all libs for threadsafety
+#
+Dir["#{Rails.root}/lib/**/*.rb"].each { |file| require file }
diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample
index 333c388f5ff..e62ad0f4b71 100644
--- a/config/initializers/smtp_settings.rb.sample
+++ b/config/initializers/smtp_settings.rb.sample
@@ -1,7 +1,7 @@
# 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 smpt_settings.rb
+# 2. Rename this file to smtp_settings.rb
# 3. Edit settings inside this file
# 4. Restart GitLab instance
#
diff --git a/config/puma.rb.example b/config/puma.rb.example
index ad5e3e23501..08eace369fd 100644
--- a/config/puma.rb.example
+++ b/config/puma.rb.example
@@ -93,6 +93,13 @@ bind "unix://#{application_path}/tmp/sockets/gitlab.socket"
#
# workers 2
+# GitLab cluster mode recommendations
+# If you have more than 1 GB RAM, uncomment one of the following lines:
+#
+# workers 2 # if you have at least 1.5 GB RAM
+# workers 3 # if you have at least 2 GB RAM
+# workers 4 # if you have at least 2.5 GB RAM
+
# Code to run when a worker boots to setup the process before booting
# the app.
#
diff --git a/config/routes.rb b/config/routes.rb
index 0b0bd293da2..f545ad82f77 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -29,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'
@@ -39,6 +40,16 @@ Gitlab::Application.routes.draw do
get 'help/workflow' => 'help#workflow'
#
+ # Global snippets
+ #
+ resources :snippets do
+ member do
+ get "raw"
+ end
+ end
+ get "/s/:username" => "snippets#user_index", as: :user_snippets, constraints: { username: /.*/ }
+
+ #
# Public namespace
#
namespace :public do
@@ -83,7 +94,7 @@ Gitlab::Application.routes.draw do
end
resource :logs, only: [:show]
- resource :resque, controller: 'resque', only: [:show]
+ resource :background_jobs, controller: 'background_jobs', only: [:show]
resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show] do
scope module: :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
@@ -112,6 +123,7 @@ Gitlab::Application.routes.draw do
end
resource :notifications
+ resource :password
end
resources :keys
@@ -179,9 +191,18 @@ Gitlab::Application.routes.draw do
resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
resources :compare, only: [:index, :create]
resources :blame, only: [:show], constraints: {id: /.+/}
- resources :graph, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
+ 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: /.+/}
+ scope module: :projects do
+ resources :snippets do
+ member do
+ get "raw"
+ end
+ end
+ end
+
resources :wikis, only: [:show, :edit, :destroy, :create] do
collection do
get :pages
@@ -231,11 +252,11 @@ Gitlab::Application.routes.draw do
member do
# tree viewer logs
- get "logs_tree", constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }
+ 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\/_\-]+/,
+ id: /[a-zA-Z.0-9\/_\-#%+]+/,
path: /.*/
}
end
@@ -255,19 +276,12 @@ Gitlab::Application.routes.draw do
end
end
- resources :snippets do
- member do
- get "raw"
- end
- end
-
resources :hooks, only: [:index, :create, :destroy] do
member do
get :test
end
end
-
resources :team, controller: 'team_members', only: [:index]
resources :milestones, except: [:destroy]
@@ -305,7 +319,10 @@ Gitlab::Application.routes.draw do
end
end
- resources :notes, only: [:index, :create, :destroy] do
+ resources :notes, only: [:index, :create, :destroy, :update] do
+ member do
+ delete :delete_attachment
+ end
collection do
post :preview
end
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
new file mode 100644
index 00000000000..00c509dd829
--- /dev/null
+++ b/config/unicorn.rb.example
@@ -0,0 +1,102 @@
+# 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.
+
+# 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
+
+# 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"
+
+# Help ensure your application will always spawn in the symlinked
+# "current" directory that Capistrano sets up.
+working_directory "/home/git/gitlab/current" # available in 0.94.0+
+
+# 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 8080, :tcp_nopush => true
+
+# nuke workers after 30 seconds instead of 60 seconds (the default)
+timeout 30
+
+# feel free to point this anywhere accessible on the filesystem
+pid "/home/git/gitlab/tmp/pids/unicorn.pid"
+
+# By default, the Unicorn logger will write to stderr.
+# Additionally, ome 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
+
+# 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!
+
+ # 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.
+ #
+ # # 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 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
+ # 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|
+ # 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
+
+ # 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/09_issues.rb b/db/fixtures/development/09_issues.rb
index d13d520e3dd..627579721d0 100644
--- a/db/fixtures/development/09_issues.rb
+++ b/db/fixtures/development/09_issues.rb
@@ -11,7 +11,7 @@ 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,
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index d122d96235e..0a8d67d4461 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -17,7 +17,8 @@ Gitlab::Seeder.quiet do
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: branches.first,
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index f119694d11d..632f6107b33 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -3,7 +3,8 @@ admin = User.create(
name: "Administrator",
username: 'root',
password: "5iveL!fe",
- password_confirmation: "5iveL!fe"
+ password_confirmation: "5iveL!fe",
+ password_expires_at: Time.now
)
admin.projects_limit = 10000
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/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/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/schema.rb b/db/schema.rb
index 6a16caf59d8..348272e0832 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20130522141856) do
+ActiveRecord::Schema.define(:version => 20130614132337) do
create_table "deploy_keys_projects", :force => true do |t|
t.integer "deploy_key_id", :null => false
@@ -172,6 +172,7 @@ ActiveRecord::Schema.define(:version => 20130522141856) do
t.string "issues_tracker_id"
t.boolean "snippets_enabled", :default => true, :null => false
t.datetime "last_activity_at"
+ t.boolean "imported", :default => false, :null => false
end
add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id"
@@ -203,12 +204,14 @@ ActiveRecord::Schema.define(:version => 20130522141856) do
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.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", ["created_at"], :name => "index_snippets_on_created_at"
@@ -290,9 +293,12 @@ ActiveRecord::Schema.define(:version => 20130522141856) do
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", ["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"
diff --git a/doc/api/README.md b/doc/api/README.md
index c4d44c52748..9d6e229e4cb 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -69,13 +69,20 @@ 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)
-+ [System Hooks](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/system_hooks.md)
++ [Users](api/users.md)
++ [Session](api/session.md)
++ [Projects](api/projects.md)
++ [Project Snippets](api/project_snippets.md)
++ [Repositories](api/repositories.md)
++ [Issues](api/issues.md)
++ [Milestones](api/milestones.md)
++ [Notes](api/notes.md)
++ [Deploy Keys](api/deploy_keys.md)
++ [System Hooks](api/system_hooks.md)
++ [Groups](api/groups.md)
++ [User Teams](api/user_teams.md)
+
+## Clients
+
++ [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
++ [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
new file mode 100644
index 00000000000..5a394094fe6
--- /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/issues.md b/doc/api/issues.md
index a8ae7401b39..87547593586 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -25,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"
},
@@ -42,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"
},
@@ -62,7 +62,7 @@ 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"
}
@@ -111,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"
},
@@ -131,7 +131,7 @@ 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"
}
@@ -173,7 +173,7 @@ 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**)
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 92a29cee954..aa8f1bf5e02 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -56,5 +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/snippets.md b/doc/api/project_snippets.md
index 04ea367d518..04ea367d518 100644
--- a/doc/api/snippets.md
+++ b/doc/api/project_snippets.md
diff --git a/doc/api/projects.md b/doc/api/projects.md
index d3d15ab4e02..41b6b6add39 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -30,7 +30,8 @@ GET /projects
"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,
@@ -52,7 +53,8 @@ GET /projects
"merge_requests_enabled": true,
"wall_enabled": true,
"wiki_enabled": true,
- "created_at": "2012-05-30T12:49:20Z"
+ "created_at": "2012-05-30T12:49:20Z",
+ "last_activity_at": "2012-05-23T08:05:02Z"
}
]
```
@@ -75,6 +77,7 @@ Parameters:
{
"id": 5,
"name": "gitlab",
+ "name_with_namespace": "GitLab / gitlabhq",
"description": null,
"default_branch": "api",
"owner": {
@@ -92,10 +95,79 @@ Parameters:
"merge_requests_enabled": true,
"wall_enabled": true,
"wiki_enabled": true,
- "created_at": "2012-05-30T12:49:20Z"
+ "created_at": "2012-05-30T12:49:20Z",
+ "last_activity_at": "2012-05-23T08:05:02Z"
}
```
+### 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 project
@@ -382,125 +454,27 @@ Parameters:
+ `branch` (required) - The name of the branch.
-### List tags
-
-Lists all tags of a project.
-
-```
-GET /projects/:id/repository/tags
-```
-
-Parameters:
-
-+ `id` (required) - The ID of the project
-
-
-### List commits
-
-Lists all commits with pagination. If the optional `ref_name` name is not given the commits of
-the default branch (usually master) are returned.
-
-```
-GET /projects/:id/repository/commits
-```
-
-Parameters:
-
-+ `id` (required) - The Id of the project
-+ `ref_name` (optional) - The name of a repository branch or tag
-+ `page` (optional) - The page of commits to return (`0` default)
-+ `per_page` (optional) - The number of commits per page (`20` default)
-
-Returns values:
-
-+ `200 Ok` on success and a list with commits
-+ `404 Not Found` if project with id or the branch with `ref_name` not found
-
+## Admin fork relation
+Allows modification of the forked relationship between existing projects. . Available only for admins.
-## Deploy Keys
-
-### List deploy keys
-
-Get a list of a project's deploy keys.
+### Create a forked from/to relation between existing projects.
```
-GET /projects/:id/keys
+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
-```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.
+### Delete an existing forked from relationship
```
-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="
-}
+DELETE /projects/:id/fork
```
+Parameter:
-### Add deploy key
-
-Creates a new deploy key for a project.
-
-```
-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
-
++ `id` (required) - The ID of the project \ No newline at end of file
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 90fda387947..7a9f766ba1d 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -239,6 +239,56 @@ Parameters:
]
```
+## 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
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 ee5e98a016c..9dd35f4bca0 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -14,32 +14,32 @@ 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
+ "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
+ "color_scheme_id": 3
}
]
```
@@ -63,16 +63,16 @@ 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
+ "color_scheme_id": 2
}
```
@@ -156,14 +156,14 @@ GET /user
"email": "john@example.com",
"name": "John Smith",
"private_token": "dd34asd13as",
- "blocked": false,
+ "state": "active",
"created_at": "2012-05-23T08:00:58Z",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
- "dark_scheme": false,
"theme_id": 1
+ "color_scheme_id": 2
"is_admin": false,
"can_create_group" : true,
"can_create_team" : true,
diff --git a/doc/install/databases.md b/doc/install/databases.md
index 2d94aa9b4a3..5ec1d0c6524 100644
--- a/doc/install/databases.md
+++ b/doc/install/databases.md
@@ -11,10 +11,17 @@ 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
+
# Login to MySQL
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
@@ -29,6 +36,16 @@ GitLab supports the following databases:
# Try connecting to the new database with the new user
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
# Install the database packages
diff --git a/doc/install/installation.md b/doc/install/installation.md
index a80e2a8dd5f..6cad280acaf 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -1,12 +1,20 @@
+# 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).
+
+![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 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.
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.
+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).
+If you find a bug/error in this guide please **submit a pull request** following the [contributing guide](../../CONTRIBUTING.md).
- - -
@@ -28,17 +36,19 @@ The GitLab installation consists of setting up the following components:
`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
+ # 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:
@@ -47,7 +57,7 @@ Install the required packages:
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
@@ -65,15 +75,17 @@ Make sure you have the right version of Python installed.
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 postfix
+ sudo apt-get install -y postfix
+
+Then select 'Internet Site' and press enter to confirm the hostname.
# 2. Ruby
-Remove old 1.8 ruby if present
+Remove the old Ruby 1.8 if present
- sudo apt-get remove ruby1.8
+ sudo apt-get remove -y ruby1.8
-Download and compile it:
+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-p392.tar.gz | tar xz
@@ -84,7 +96,7 @@ Download and compile it:
Install the Bundler Gem:
- sudo gem install bundler
+ sudo gem install bundler --no-ri --no-rdoc
# 3. System Users
@@ -98,28 +110,25 @@ Create a `git` user for Gitlab:
GitLab Shell is a ssh access and repository management software developed specially for GitLab.
- # Login as git
- sudo su git
-
# Go to home directory
cd /home/git
# Clone gitlab shell
- git clone https://github.com/gitlabhq/gitlab-shell.git
+ sudo -u git -H git clone https://github.com/gitlabhq/gitlab-shell.git
cd gitlab-shell
# switch to right version
- git checkout v1.4.0
+ sudo -u git -H git checkout v1.4.0
- cp config.yml.example config.yml
+ sudo -u git -H cp config.yml.example config.yml
# Edit config and replace gitlab_url
# with something like 'http://domain.com/'
- vim config.yml
+ sudo -u git -H editor config.yml
# Do setup
- ./bin/install
+ sudo -u git -H ./bin/install
# 5. Database
@@ -141,11 +150,10 @@ To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install
cd /home/git/gitlab
# Checkout to stable release
- sudo -u git -H git checkout 5-2-stable
+ sudo -u git -H git checkout 5-3-stable
**Note:**
-You can change `5-2-stable` to `master` if you want the *bleeding edge* version, but
-do so with caution!
+You can change `5-3-stable` to `master` if you want the *bleeding edge* version, but do so with caution!
## Configure it
@@ -156,7 +164,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/
@@ -180,6 +188,10 @@ do so with caution!
# Copy the example Puma config
sudo -u git -H cp config/puma.rb.example config/puma.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/puma.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"
@@ -193,10 +205,21 @@ Make sure to edit both `gitlab.yml` and `puma.rb` to match your setup.
# 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
@@ -204,23 +227,27 @@ Make sure to update username/password in config/database.yml.
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 unicorn 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 unicorn 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/gitlabhq/5-2-stable/lib/support/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:
@@ -234,6 +261,14 @@ 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
@@ -241,12 +276,6 @@ 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
@@ -255,22 +284,20 @@ If you can't or don't want to use Nginx as your web server, have a look at the
[`Advanced Setup Tips`](./installation.md#advanced-setup-tips) section.
## 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/gitlabhq/5-2-stable/lib/support/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:
- # **YOUR_SERVER_FQDN** to the fully-qualified
- # domain name of your host serving GitLab. Also, replace
- # the 'listen' line with the following:
- # listen 80 default_server; # e.g., listen 192.168.1.1:80;
- sudo vim /etc/nginx/sites-available/gitlab
+ # Change YOUR_SERVER_FQDN to the fully-qualified
+ # domain name of your host serving GitLab.
+ sudo editor /etc/nginx/sites-available/gitlab
## Restart
@@ -330,10 +357,10 @@ GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already
These steps are fairly general and you will need to figure out the exact details from the Omniauth provider's documentation.
-* Add `gem "omniauth-your-auth-provider"` to the [Gemfile](https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/Gemfile#L18)
+* Add `gem "omniauth-your-auth-provider"` to the [Gemfile](https://github.com/gitlabhq/gitlabhq/blob/5-3-stable/Gemfile#L18)
* Run `sudo -u git -H bundle install` to install the new gem(s)
-* 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/5-2-stable/config/gitlab.yml.example#L53) as a reference)
-* Add icons for the new provider into the [vendor/assets/images/authbuttons](https://github.com/gitlabhq/gitlabhq/tree/5-2-stable/vendor/assets/images/authbuttons) directory (you can find some more popular ones over at https://github.com/intridea/authbuttons)
+* 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/5-3-stable/config/gitlab.yml.example#L53) as a reference)
+* Add icons for the new provider into the [vendor/assets/images/authbuttons](https://github.com/gitlabhq/gitlabhq/tree/5-3-stable/vendor/assets/images/authbuttons) directory (you can find some more popular ones over at https://github.com/intridea/authbuttons)
* Restart GitLab
### Examples
diff --git a/doc/make_release.md b/doc/make_release.md
new file mode 100644
index 00000000000..24f8397f10b
--- /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 strucuture 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/puma.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..3ff3ab9a5ae
--- /dev/null
+++ b/doc/markdown/markdown.md
@@ -0,0 +1,451 @@
+
+----------------------------------------------
+
+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)
+
+[References](#toc_28)
+---------------------
+
+----------------------------------------------
+
+<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="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/maintenance.md b/doc/raketasks/maintenance.md
index 13f238df3e9..68c1a72b230 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -116,6 +116,8 @@ bundle exec rake gitlab:satellites:create RAILS_ENV=production
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:
@@ -132,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/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md
index 25fe2f50e25..45fc3436ebe 100644
--- a/doc/update/5.0-to-5.1.md
+++ b/doc/update/5.0-to-5.1.md
@@ -23,8 +23,14 @@ sudo -u git -H git checkout 5-1-stable
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
@@ -35,6 +41,7 @@ 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
diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md
index 8f2116837b8..23de2d99c58 100644
--- a/doc/update/5.1-to-5.2.md
+++ b/doc/update/5.1-to-5.2.md
@@ -1,5 +1,15 @@
# 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
@@ -20,14 +30,68 @@ sudo -u git -H git fetch
sudo -u git -H git checkout v1.4.0
```
-### 4. Install libs, migrations etc
+### 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
```
-### 5. Start application
+### 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..4d930d84c7d
--- /dev/null
+++ b/doc/update/5.2-to-5.3.md
@@ -0,0 +1,80 @@
+# 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
+```
+
+### 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/features/admin/groups.feature b/features/admin/groups.feature
index 28f35e3a831..054dccfd64c 100644
--- a/features/admin/groups.feature
+++ b/features/admin/groups.feature
@@ -11,6 +11,7 @@ Feature: Admin Groups
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/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/group/group.feature b/features/group/group.feature
index a48affe8e02..64424f47236 100644
--- a/features/group/group.feature
+++ b/features/group/group.feature
@@ -19,6 +19,7 @@ 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
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/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/project.feature b/features/project/project.feature
index 0c8c97c816a..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
diff --git a/features/project/snippets.feature b/features/project/snippets.feature
new file mode 100644
index 00000000000..a26c8dc8474
--- /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 "Destroy"
+ Then I should not see "Snippet one" in snippets
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 a2b49070f9f..d780d9c96d9 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)
@@ -40,8 +41,8 @@ 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"
end
click_button "Add user to projects in group"
@@ -49,8 +50,6 @@ class AdminGroups < Spinach::FeatureSteps
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")
end
protected
diff --git a/features/steps/admin/admin_teams.rb b/features/steps/admin/admin_teams.rb
index 65c7e485f48..066fc3fa603 100644
--- a/features/steps/admin/admin_teams.rb
+++ b/features/steps/admin/admin_teams.rb
@@ -85,7 +85,7 @@ class AdminTeams < Spinach::FeatureSteps
end
Then 'I should see empty projects table' do
- page.has_no_css?("#projects_list").must_equal true
+ page.should have_content "Projects (0)"
end
When 'I select project "Shop" with max access "Reporter"' do
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/group/group.rb b/features/steps/group/group.rb
index 8b5a4ed44df..102bc440d82 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|
@@ -39,7 +40,7 @@ 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"
+ select2(user.id, from: "#user_ids", multiple: true)
select "Reporter", from: "project_access"
end
click_button "Add"
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 8981705df48..c1fe00c8e65 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -74,7 +74,7 @@ class Profile < Spinach::FeatureSteps
When "I change my code preview theme" do
within '.code-preview-theme' do
- choose "Solarized Dark"
+ choose "Solarized dark"
end
end
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index fd9dce7fe30..7f7492bfd6d 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -35,6 +35,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
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
diff --git a/features/steps/project/project_active_tab.rb b/features/steps/project/project_active_tab.rb
index 4c6d890fe4b..5170ab82e8a 100644
--- a/features/steps/project/project_active_tab.rb
+++ b/features/steps/project/project_active_tab.rb
@@ -45,7 +45,7 @@ class ProjectActiveTab < Spinach::FeatureSteps
# Sub Tabs: Home
Given 'I click the "Team" tab' do
- click_link('Team')
+ click_link('Project Members')
end
Given 'I click the "Attachments" tab' do
@@ -61,7 +61,7 @@ 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
@@ -73,7 +73,7 @@ class ProjectActiveTab < Spinach::FeatureSteps
end
Then 'the active sub tab should be Team' do
- ensure_active_sub_tab('Team')
+ ensure_active_sub_tab('Project Members')
end
Then 'the active sub tab should be Attachments' do
@@ -89,7 +89,7 @@ class ProjectActiveTab < Spinach::FeatureSteps
end
Then 'the active sub tab should be Hooks' do
- ensure_active_sub_tab('Hooks')
+ ensure_active_sub_tab('Web Hooks')
end
Then 'the active sub tab should be Deploy Keys' do
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_merge_requests.rb b/features/steps/project/project_merge_requests.rb
index 6a2f870e276..ea434412bb2 100644
--- a/features/steps/project/project_merge_requests.rb
+++ b/features/steps/project/project_merge_requests.rb
@@ -57,8 +57,8 @@ class ProjectMergeRequests < Spinach::FeatureSteps
And 'I submit new merge request "Wiki Feature"' do
fill_in "merge_request_title", with: "Wiki Feature"
- select "master", from: "merge_request_source_branch"
- select "stable", from: "merge_request_target_branch"
+ select "bootstrap", from: "merge_request_source_branch"
+ select "master", from: "merge_request_target_branch"
click_button "Submit merge request"
end
diff --git a/features/steps/project/project_milestones.rb b/features/steps/project/project_milestones.rb
index fcd590fcab2..c4d0d176f3a 100644
--- a/features/steps/project/project_milestones.rb
+++ b/features/steps/project/project_milestones.rb
@@ -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 48a73f09fac..f001c0beb9a 100644
--- a/features/steps/project/project_network_graph.rb
+++ b/features/steps/project/project_network_graph.rb
@@ -12,7 +12,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
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
diff --git a/features/steps/project/project_snippets.rb b/features/steps/project/project_snippets.rb
new file mode 100644
index 00000000000..2634ea192bf
--- /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 "Destroy"' do
+ click_link "Destroy"
+ 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 "Save"
+ 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/shared/paths.rb b/features/steps/shared/paths.rb
index b0d3b0f2a03..21b6159bce3 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -106,7 +106,7 @@ module SharedPaths
end
step 'I visit admin Resque page' do
- visit admin_resque_path
+ visit admin_background_jobs_path
end
step 'I visit admin groups page' do
@@ -149,7 +149,7 @@ module SharedPaths
# 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
step "I visit my project's issues page" do
@@ -275,6 +275,22 @@ module SharedPaths
visit public_root_path
end
+ # ----------------------------------------
+ # 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
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..1fac8d0f988
--- /dev/null
+++ b/features/steps/snippets/snippets.rb
@@ -0,0 +1,65 @@
+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"
+ select "forever", :from => "personal_snippet_expires_at"
+ 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 "Save"
+ 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
index 9a86572e1ac..b4b2fb66a50 100644
--- a/features/steps/userteams/userteams.rb
+++ b/features/steps/userteams/userteams.rb
@@ -93,7 +93,7 @@ class Userteams < Spinach::FeatureSteps
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|
+ project.issues.assigned_to(current_user).each do |issue|
page.should have_content issue.title
end
end
@@ -121,7 +121,7 @@ class Userteams < Spinach::FeatureSteps
team = UserTeam.last
team.projects.each do |project|
team.members.each do |member|
- project.issues.assigned(member).each do |issue|
+ project.issues.assigned_to(member).each do |issue|
page.should have_content issue.title
end
end
@@ -131,9 +131,7 @@ class Userteams < Spinach::FeatureSteps
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 { create(:merge_request, assignee: member, project: project) }
- end
+ create(:merge_request, assignee: current_user, project: project)
end
end
@@ -145,10 +143,8 @@ class Userteams < Spinach::FeatureSteps
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
+ project.merge_requests.each do |merge_request|
+ page.should have_content merge_request.title
end
end
end
@@ -156,20 +152,8 @@ class Userteams < Spinach::FeatureSteps
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 { create(:merge_request, assignee: member, project: project) }
- 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
+ member = team.members.sample
+ create(:merge_request, assignee: member, project: project)
end
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index c2752406122..5d97d50cb82 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -35,5 +35,9 @@ module API
mount Notes
mount Internal
mount SystemHooks
+ mount UserTeams
+ 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 c3c351e1733..dea5771d6b6 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -2,7 +2,7 @@ module API
module Entities
class User < Grape::Entity
expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter,
- :dark_scheme, :theme_id, :state, :created_at, :extern_uid, :provider
+ :theme_id, :color_scheme_id, :state, :created_at, :extern_uid, :provider
end
class UserSafe < Grape::Entity
@@ -25,13 +25,20 @@ module API
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 :public
+ 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, :created_at, :last_activity_at
expose :namespace
+ expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
end
class ProjectMember < UserBasic
@@ -40,6 +47,18 @@ module API
end
end
+ class TeamMember < UserBasic
+ expose :permission, as: :access_level do |user, options|
+ options[:user_team].user_team_user_relationships.find_by_user_id(user.id).permission
+ end
+ end
+
+ class TeamProject < Project
+ expose :greatest_access, as: :greatest_access_level do |project, options|
+ options[:user_team].user_team_project_relationships.find_by_project_id(project.id).greatest_access
+ end
+ end
+
class Group < Grape::Entity
expose :id, :name, :path, :owner_id
end
@@ -87,6 +106,10 @@ module API
expose :id, :title, :key, :created_at
end
+ class UserTeam < Grape::Entity
+ expose :id, :name, :path, :owner_id
+ end
+
class MergeRequest < Grape::Entity
expose :id, :target_branch, :source_branch, :project_id, :title, :state
expose :author, :assignee, using: Entities::UserBasic
@@ -104,5 +127,11 @@ module API
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/helpers.rb b/lib/api/helpers.rb
index 94cf4f2e69f..f857d4133b2 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -5,12 +5,12 @@ module API
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
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 53e2e8cfa35..a15203d1563 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -2,6 +2,7 @@ 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
@@ -70,7 +71,7 @@ module API
# 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
- # state (optional) - The state of an issue (close|reopen)
+ # 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
@@ -79,7 +80,7 @@ module API
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 23e2f82889f..861a4f4d159 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -2,6 +2,7 @@ module API
# MergeRequest API
class MergeRequests < Grape::API
before { authenticate! }
+ before { Thread.current[:current_user] = current_user }
resource :projects do
helpers do
@@ -94,8 +95,6 @@ module API
authorize! :modify_merge_request, merge_request
- MergeRequestObserver.current_user = current_user
-
if merge_request.update_attributes attrs
merge_request.reload_code
merge_request.mark_as_unchecked
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index a25bbad1302..aee12e7dc40 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -60,7 +60,7 @@ module API
# title (optional) - The title of a milestone
# description (optional) - The description of a milestone
# due_date (optional) - The due date of a milestone
- # state (optional) - The status of the milestone (close|activate)
+ # 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
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 ddc403c12db..d5709f5cb59 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -22,6 +22,15 @@ module API
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:
@@ -32,6 +41,20 @@ module API
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:
@@ -98,6 +121,42 @@ module API
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
#
# Parameters:
@@ -194,244 +253,6 @@ module API
{message: "Access revoked", id: params[:user_id].to_i}
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
-
- # 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
- 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_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_snippet, user_project
- @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])
- content_type 'text/plain'
- present @snippet.content
- 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
- #
- # 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]
- 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/repositories.rb b/lib/api/repositories.rb
index 964b9eb38ac..5db17b7e414 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -94,7 +94,7 @@ module API
get ":id/repository/commits" do
authorize! :download_code, user_project
- page = params[:page] || 0
+ page = (params[:page] || 0).to_i
per_page = (params[:per_page] || 20).to_i
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
@@ -102,6 +102,31 @@ module API
present commits, with: Entities::RepoCommit
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:
@@ -124,6 +149,8 @@ module API
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
diff --git a/lib/api/user_teams.rb b/lib/api/user_teams.rb
new file mode 100644
index 00000000000..05aa72f0e92
--- /dev/null
+++ b/lib/api/user_teams.rb
@@ -0,0 +1,276 @@
+module API
+ # user_teams API
+ class UserTeams < Grape::API
+ before { authenticate! }
+
+ resource :user_teams do
+ helpers do
+ def handle_team_member_errors(errors)
+ if errors[:permission].any?
+ render_api_error!(errors[:permission], 422)
+ end
+ not_found!
+ end
+
+ def validate_access_level?(level)
+ [UsersProject::GUEST, UsersProject::REPORTER, UsersProject::DEVELOPER, UsersProject::MASTER].include? level.to_i
+ end
+ end
+
+
+ # Get a user_teams list
+ #
+ # Example Request:
+ # GET /user_teams
+ get do
+ if current_user.admin
+ @user_teams = paginate UserTeam
+ else
+ @user_teams = paginate current_user.user_teams
+ end
+ present @user_teams, with: Entities::UserTeam
+ end
+
+
+ # Create user_team. Available only for admin
+ #
+ # Parameters:
+ # name (required) - The name of the user_team
+ # path (required) - The path of the user_team
+ # Example Request:
+ # POST /user_teams
+ post do
+ authenticated_as_admin!
+ required_attributes! [:name, :path]
+
+ attrs = attributes_for_keys [:name, :path]
+ @user_team = UserTeam.new(attrs)
+ @user_team.owner = current_user
+
+ if @user_team.save
+ present @user_team, with: Entities::UserTeam
+ else
+ not_found!
+ end
+ end
+
+
+ # Get a single user_team
+ #
+ # Parameters:
+ # id (required) - The ID of a user_team
+ # Example Request:
+ # GET /user_teams/:id
+ get ":id" do
+ @user_team = UserTeam.find(params[:id])
+ if current_user.admin or current_user.user_teams.include? @user_team
+ present @user_team, with: Entities::UserTeam
+ else
+ not_found!
+ end
+ end
+
+
+ # Get user_team members
+ #
+ # Parameters:
+ # id (required) - The ID of a user_team
+ # Example Request:
+ # GET /user_teams/:id/members
+ get ":id/members" do
+ @user_team = UserTeam.find(params[:id])
+ if current_user.admin or current_user.user_teams.include? @user_team
+ @members = paginate @user_team.members
+ present @members, with: Entities::TeamMember, user_team: @user_team
+ else
+ not_found!
+ end
+ end
+
+
+ # Add a new user_team member
+ #
+ # Parameters:
+ # id (required) - The ID of a user_team
+ # user_id (required) - The ID of a user
+ # access_level (required) - Project access level
+ # Example Request:
+ # POST /user_teams/:id/members
+ post ":id/members" do
+ authenticated_as_admin!
+ required_attributes! [:user_id, :access_level]
+
+ if not validate_access_level?(params[:access_level])
+ render_api_error!("Wrong access level", 422)
+ end
+
+ @user_team = UserTeam.find(params[:id])
+ if @user_team
+ team_member = @user_team.user_team_user_relationships.find_by_user_id(params[:user_id])
+ # Not existing member
+ if team_member.nil?
+ @user_team.add_member(params[:user_id], params[:access_level], false)
+ team_member = @user_team.user_team_user_relationships.find_by_user_id(params[:user_id])
+
+ if team_member.nil?
+ render_api_error!("Error creating membership", 500)
+ else
+ @member = team_member.user
+ present @member, with: Entities::TeamMember, user_team: @user_team
+ end
+ else
+ render_api_error!("Already exists", 409)
+ end
+ else
+ not_found!
+ end
+ end
+
+
+ # Get a single team member from user_team
+ #
+ # Parameters:
+ # id (required) - The ID of a user_team
+ # user_id (required) - The ID of a team member
+ # Example Request:
+ # GET /user_teams/:id/members/:user_id
+ get ":id/members/:user_id" do
+ @user_team = UserTeam.find(params[:id])
+ if current_user.admin or current_user.user_teams.include? @user_team
+ team_member = @user_team.user_team_user_relationships.find_by_user_id(params[:user_id])
+ unless team_member.nil?
+ present team_member.user, with: Entities::TeamMember, user_team: @user_team
+ else
+ not_found!
+ end
+ else
+ not_found!
+ end
+ end
+
+ # Remove a team member from user_team
+ #
+ # Parameters:
+ # id (required) - The ID of a user_team
+ # user_id (required) - The ID of a team member
+ # Example Request:
+ # DELETE /user_teams/:id/members/:user_id
+ delete ":id/members/:user_id" do
+ authenticated_as_admin!
+
+ @user_team = UserTeam.find(params[:id])
+ if @user_team
+ team_member = @user_team.user_team_user_relationships.find_by_user_id(params[:user_id])
+ unless team_member.nil?
+ team_member.destroy
+ else
+ not_found!
+ end
+ else
+ not_found!
+ end
+ end
+
+
+ # Get to user_team assigned projects
+ #
+ # Parameters:
+ # id (required) - The ID of a user_team
+ # Example Request:
+ # GET /user_teams/:id/projects
+ get ":id/projects" do
+ @user_team = UserTeam.find(params[:id])
+ if current_user.admin or current_user.user_teams.include? @user_team
+ @projects = paginate @user_team.projects
+ present @projects, with: Entities::TeamProject, user_team: @user_team
+ else
+ not_found!
+ end
+ end
+
+
+ # Add a new user_team project
+ #
+ # Parameters:
+ # id (required) - The ID of a user_team
+ # project_id (required) - The ID of a project
+ # greatest_access_level (required) - Project access level
+ # Example Request:
+ # POST /user_teams/:id/projects
+ post ":id/projects" do
+ authenticated_as_admin!
+ required_attributes! [:project_id, :greatest_access_level]
+
+ if not validate_access_level?(params[:greatest_access_level])
+ render_api_error!("Wrong greatest_access_level", 422)
+ end
+
+ @user_team = UserTeam.find(params[:id])
+ if @user_team
+ team_project = @user_team.user_team_project_relationships.find_by_project_id(params[:project_id])
+
+ # No existing project
+ if team_project.nil?
+ @user_team.assign_to_projects([params[:project_id]], params[:greatest_access_level])
+ team_project = @user_team.user_team_project_relationships.find_by_project_id(params[:project_id])
+ if team_project.nil?
+ render_api_error!("Error creating project assignment", 500)
+ else
+ @project = team_project.project
+ present @project, with: Entities::TeamProject, user_team: @user_team
+ end
+ else
+ render_api_error!("Already exists", 409)
+ end
+ else
+ not_found!
+ end
+ end
+
+ # Show a single team project from user_team
+ #
+ # Parameters:
+ # id (required) - The ID of a user_team
+ # project_id (required) - The ID of a project assigned to the team
+ # Example Request:
+ # GET /user_teams/:id/projects/:project_id
+ get ":id/projects/:project_id" do
+ @user_team = UserTeam.find(params[:id])
+ if current_user.admin or current_user.user_teams.include? @user_team
+ team_project = @user_team.user_team_project_relationships.find_by_project_id(params[:project_id])
+ unless team_project.nil?
+ present team_project.project, with: Entities::TeamProject, user_team: @user_team
+ else
+ not_found!
+ end
+ else
+ not_found!
+ end
+ end
+
+ # Remove a team project from user_team
+ #
+ # Parameters:
+ # id (required) - The ID of a user_team
+ # project_id (required) - The ID of a project assigned to the team
+ # Example Request:
+ # DELETE /user_teams/:id/projects/:project_id
+ delete ":id/projects/:project_id" do
+ authenticated_as_admin!
+
+ @user_team = UserTeam.find(params[:id])
+ if @user_team
+ team_project = @user_team.user_team_project_relationships.find_by_project_id(params[:project_id])
+ unless team_project.nil?
+ team_project.destroy
+ else
+ not_found!
+ end
+ else
+ not_found!
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 62a510f2acc..c5e3d049fd7 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -71,7 +71,7 @@ module Backup
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")
+ 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
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index 4a38af02522..462d3f1e274 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -15,11 +15,15 @@ module Backup
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
-
- FileUtils.cp_r(backup_uploads_dir, app_uploads_dir)
end
end
end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index ff1d1b13cc9..beb4fcf0570 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -39,8 +39,7 @@ module Gitlab
email: email,
password: password,
password_confirmation: password,
- projects_limit: Gitlab.config.gitlab.default_projects_limit,
- }, as: :admin)
+ }, as: :admin).with_defaults
@user.save!
if Gitlab.config.omniauth['block_auto_created_users'] && !ldap
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 4f3f7b02a5b..e7217c7c7e6 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -1,9 +1,13 @@
require_relative 'shell_env'
-require 'omniauth-ldap'
+require_relative 'grack_ldap'
+require_relative 'grack_helpers'
module Grack
class Auth < Rack::Auth::Basic
- attr_accessor :user, :project
+ include LDAP
+ include Helpers
+
+ attr_accessor :user, :project, :ref, :env
def call(env)
@env = env
@@ -14,42 +18,52 @@ module Grack
@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)
- else
- unauthorized
- end
+ 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
- @user = authenticate(login, password)
- return false unless @user
+ @user = authenticate_user(login, password)
- Gitlab::ShellEnv.set_env(@user)
+ 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 authenticate(login, password)
+ def authenticate_user(login, password)
user = User.find_by_email(login) || User.find_by_username(login)
# If the provided login was not a known email or username
@@ -65,34 +79,12 @@ module Grack
end
end
- def ldap_auth(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.blank? && !password.blank?
-
- ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf)
- ldap_user = ldap.bind_as(
- filter: Net::LDAP::Filter.eq(ldap.uid, login),
- size: 1,
- password: password
- )
-
- User.find_by_extern_uid_and_provider(ldap_user.dn, 'ldap') if ldap_user
- end
-
- def validate_get_request
- validate_request(@request.params['service'])
- end
-
- def validate_post_request
- validate_request(File.basename(@request.path))
- end
-
- def validate_request(service)
- if service == 'git-upload-pack'
+ def authorize_request(service)
+ case service
+ when 'git-upload-pack'
project.public || can?(user, :download_code, project)
- elsif service == '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
@@ -104,49 +96,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\.-]+)/n.match(input.force_encoding('ascii-8bit')).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
- end
-
- def ldap_conf
- @ldap_conf ||= Gitlab.config.ldap
+ # 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/grack_ldap.rb b/lib/gitlab/backend/grack_ldap.rb
new file mode 100644
index 00000000000..45e98fbac1e
--- /dev/null
+++ b/lib/gitlab/backend/grack_ldap.rb
@@ -0,0 +1,24 @@
+require 'omniauth-ldap'
+
+module Grack
+ module LDAP
+ def ldap_auth(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.blank? && !password.blank?
+
+ ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf)
+ ldap_user = ldap.bind_as(
+ filter: Net::LDAP::Filter.eq(ldap.uid, login),
+ size: 1,
+ password: password
+ )
+
+ User.find_by_extern_uid_and_provider(ldap_user.dn, 'ldap') if ldap_user
+ end
+
+ def ldap_conf
+ @ldap_conf ||= Gitlab.config.ldap
+ end
+ end
+end
diff --git a/lib/gitlab/blacklist.rb b/lib/gitlab/blacklist.rb
new file mode 100644
index 00000000000..59203b2fbd6
--- /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)
+ end
+ end
+end
diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb
index b39fd0d552d..44cf49b4047 100644
--- a/lib/gitlab/inline_diff.rb
+++ b/lib/gitlab/inline_diff.rb
@@ -21,8 +21,9 @@ module Gitlab
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
+ diff_arr[index+1].sub!(first_token, first_token => start)
+ diff_arr[index+2].sub!(first_token, first_token => start)
last_the_same_symbols = 0
(1..max_length + 1).each do |i|
last_the_same_symbols = -i
@@ -60,8 +61,6 @@ module Gitlab
line.gsub!(FINISH, "</span>")
line
end
-
end
-
end
end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 92c9327070b..cea026f6182 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
@@ -97,7 +98,7 @@ 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
diff --git a/lib/gitlab/user_team_manager.rb b/lib/gitlab/user_team_manager.rb
index ad6b655a2ee..d5ec4ff6676 100644
--- a/lib/gitlab/user_team_manager.rb
+++ b/lib/gitlab/user_team_manager.rb
@@ -25,27 +25,30 @@ module Gitlab
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[:default_projects_access].present?
+ default_projects_access = options[:default_projects_access].to_s
- if options[:group_admin].to_s != team.admin?(member).to_s
- updates[:group_admin] = options[:group_admin].present?
+ if default_projects_access != team.default_projects_access(member).to_s
+ updates[:permission] = default_projects_access
+ end
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
+ if options[:group_admin].present?
+ group_admin = options[:group_admin].to_s == "1" ? true : false
+
+ if group_admin != team.admin?(member)
+ updates[:group_admin] = group_admin
end
- else
- true
end
+
+ return true if updates.blank?
+
+ user_team_relationship = team.user_team_user_relationships.find_by_user_id(member)
+ return false unless user_team_relationship.update_attributes(updates)
+
+ rebuild_project_permissions_to_member(team, member) if updates[:permission]
+
+ true
end
def update_project_greates_access(team, project, permission)
@@ -77,12 +80,18 @@ module Gitlab
def update_team_user_access_in_project(team, user, project, action)
granted_access = max_teams_member_permission_in_project(user, project, action)
-
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
+ if granted_access.zero?
+ project_team_user.destroy if project_team_user.present?
+ return
+ end
+
+ if project_team_user.present?
+ project_team_user.update_attributes(project_access: granted_access)
+ else
+ project.team << [user, granted_access]
+ end
end
def max_teams_member_permission_in_project(user, project, action = nil, teams = nil)
@@ -90,8 +99,8 @@ module Gitlab
teams ||= project.user_teams.with_member(user)
- if action && (action == :added) && (teams.count == 1)
- result_access ||= project.users_project.with_user(user).first.project_access
+ if action && (action == :added)
+ result_access = project.users_projects.with_user(user).first.project_access if project.users_projects.with_user(user).any?
end
if teams.any?
diff --git a/lib/gitlab/version_info.rb b/lib/gitlab/version_info.rb
index 31b72720972..6ee41e85cc9 100644
--- a/lib/gitlab/version_info.rb
+++ b/lib/gitlab/version_info.rb
@@ -5,7 +5,7 @@ module Gitlab
attr_reader :major, :minor, :patch
def self.parse(str)
- if m = str.match(/(\d+)\.(\d+)\.(\d+)/)
+ 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
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/init.d/gitlab b/lib/support/init.d/gitlab
index 4c5499bbf23..bb0151d9335 100644
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -2,7 +2,7 @@
# GITLAB
# Maintainer: @randx
-# App Version: 5.1
+# App Version: 5.2
### BEGIN INIT INFO
# Provides: gitlab
@@ -19,6 +19,7 @@ APP_ROOT="/home/git/gitlab"
APP_USER="git"
DAEMON_OPTS="-C $APP_ROOT/config/puma.rb"
PID_PATH="$APP_ROOT/tmp/pids"
+SOCKET_PATH="$APP_ROOT/tmp/sockets"
WEB_SERVER_PID="$PID_PATH/puma.pid"
SIDEKIQ_PID="$PID_PATH/sidekiq.pid"
STOP_SIDEKIQ="RAILS_ENV=production bundle exec rake sidekiq:stop"
@@ -50,6 +51,7 @@ start() {
exit 1
else
if [ `whoami` = root ]; then
+ execute "rm -f $SOCKET_PATH/gitlab.socket"
execute "RAILS_ENV=production bundle exec puma $DAEMON_OPTS"
execute "mkdir -p $PID_PATH && $START_SIDEKIQ > /dev/null 2>&1 &"
echo "$DESC started"
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index b2500659bcd..3e929c52990 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -7,8 +7,9 @@ upstream gitlab {
}
server {
- listen YOUR_SERVER_IP:80 default_server; # e.g., listen 192.168.1.1:80; In most cases *:80 is a good idea
+ 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
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index 65d99d1aea3..d071938acb5 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -90,13 +90,21 @@ namespace :gitlab do
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
+ # 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
Rake::Task["gitlab:backup:db:restore"].invoke
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index e7cfa4424ab..3d96eab0149 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -138,13 +138,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
@@ -219,7 +221,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"
@@ -239,7 +241,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"
@@ -659,7 +661,7 @@ namespace :gitlab do
current_version = Gitlab::VersionInfo.parse(gitlab_shell_version)
print "GitLab Shell version >= #{required_version} ? ... "
- if required_version <= current_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
@@ -673,7 +675,7 @@ namespace :gitlab do
puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\""
print "Git version >= #{required_version} ? ... "
- if required_version <= current_version
+ if current_version.valid? && required_version <= current_version
puts "yes (#{current_version})".green
else
puts "no".red
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index acf6abedd19..2fd7d017db8 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -13,42 +13,59 @@ namespace :gitlab do
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)
-
- # skip if not git repo
- next unless repo_name =~ /.git$/
+ path = repo_path.sub(/\.git$/, '')
+ name = File.basename path
+ group_name = File.dirname path
+ group_name = nil if group_name == '.'
- next if repo_name == 'gitolite-admin.git'
+ # Skip if group or user
+ next if namespaces.include?(name)
- path = repo_name.sub(/\.git$/, '')
+ puts "Processing #{repo_path}".yellow
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,
}
+ # 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/shell.rake b/lib/tasks/gitlab/shell.rake
index ec5451dd47c..11d4eacaa69 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -26,10 +26,12 @@ namespace :gitlab do
warn_user_is_not_gitlab
gitlab_shell_authorized_keys = File.join(File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}"),'.ssh/authorized_keys')
- puts "This will rebuild an authorized_keys file."
- puts "You will lose any data stored in #{gitlab_shell_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 #{gitlab_shell_authorized_keys}."
+ ask_to_continue
+ puts ""
+ end
system("echo '# Managed by gitlab-shell' > #{gitlab_shell_authorized_keys}")
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/factories.rb b/spec/factories.rb
index f9e25382b61..bd2ec6abf62 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
@@ -120,6 +122,7 @@ FactoryGirl.define do
factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
factory :note_on_merge_request, traits: [:on_merge_request]
factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff]
+ factory :note_on_merge_request_with_attachment, traits: [:on_merge_request, :with_attachment]
trait :on_commit do
project factory: :project_with_code
@@ -141,6 +144,10 @@ FactoryGirl.define do
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
@@ -197,7 +204,7 @@ FactoryGirl.define do
url
end
- factory :snippet do
+ factory :project_snippet do
project
author
title
@@ -205,6 +212,20 @@ FactoryGirl.define do
file_name
end
+ factory :personal_snippet do
+ author
+ title
+ content
+ file_name
+ end
+
+ factory :snippet do
+ author
+ title
+ content
+ file_name
+ end
+
factory :protected_branch do
name
project
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index a6cf5299791..15de101a17a 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -20,19 +20,24 @@ 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 "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
+ user.can_create_team.should == Gitlab.config.gitlab.default_can_create_team
+ end
+
it "should create user with valid data" do
click_button "Create user"
user = User.last
@@ -49,26 +54,13 @@ describe "Admin::Users" do
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 "Create user"
- user = User.last
- email = ActionMailer::Base.deliveries.last
- email.subject.should have_content("Account was created")
- email.text_part.body.should have_content(user.email)
- email.text_part.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 "Create user"
user = User.last
email = ActionMailer::Base.deliveries.last
email.subject.should have_content("Account was created")
email.text_part.body.should have_content(user.email)
- email.text_part.body.should_not have_content(@password)
+ email.text_part.body.should have_content('password')
end
end
end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 24f5437efff..4aa8937926c 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe "On a merge request", js: true do
let!(:project) { create(:project_with_code) }
let!(:merge_request) { create(:merge_request, project: project) }
+ let!(:note) { create(:note_on_merge_request_with_attachment, project: project) }
before do
login_as :user
@@ -65,11 +66,70 @@ describe "On a merge request", js: true do
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
- it "should be removable" do
- find('.note').hover
- find(".js-note-delete").click
- should_not have_css(".note")
+ 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
diff --git a/spec/features/snippets_spec.rb b/spec/features/snippets_spec.rb
deleted file mode 100644
index 1a0f6eaeef4..00000000000
--- a/spec/features/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 Snippet"
- 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/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 23b18fbf0eb..0f206f47234 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -10,7 +10,7 @@ describe GitlabMarkdownHelper do
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(:snippet) { create(:project_snippet, project: project) }
let(:member) { project.users_projects.where(user_id: user).first }
before do
@@ -190,8 +190,43 @@ 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
diff --git a/spec/javascripts/helpers/.gitkeep b/spec/javascripts/helpers/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ 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..a7ce97554ea 100644
--- a/spec/lib/auth_spec.rb
+++ b/spec/lib/auth_spec.rb
@@ -91,5 +91,15 @@ describe Gitlab::Auth do
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_from_omniauth(@auth, true)
+
+ 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
+ user.can_create_team.should == Gitlab.config.gitlab.default_can_create_team
+ end
end
end
diff --git a/spec/lib/gitlab/user_team_manager_spec.rb b/spec/lib/gitlab/user_team_manager_spec.rb
new file mode 100644
index 00000000000..2ee3587b367
--- /dev/null
+++ b/spec/lib/gitlab/user_team_manager_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Gitlab::UserTeamManager do
+ before do
+ @user = create :user
+ @project = create :project, creator: @user
+
+ @master = create :user
+ @developer = create :user
+ @reporter = create :user
+
+ @project.team << [@master, :master]
+ @project.team << [@developer, :developer]
+ @project.team << [@reporter, :reporter]
+
+ @team = create :user_team, owner: @user
+
+ @team.add_members([@master.id, @developer.id, @reporter.id], UsersProject::DEVELOPER, false)
+ end
+
+ it "should assign team to project with correct permissions result" do
+ @team.assign_to_project(@project, UsersProject::MASTER)
+
+ @project.users_projects.find_by_user_id(@master).project_access.should == UsersProject::MASTER
+ @project.users_projects.find_by_user_id(@developer).project_access.should == UsersProject::DEVELOPER
+ @project.users_projects.find_by_user_id(@reporter).project_access.should == UsersProject::DEVELOPER
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 84ce7e86d27..d2e1e8a8743 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -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
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index efab5510c59..d2819b7c1d6 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -39,7 +39,6 @@ describe Milestone do
end
it "should count closed issues" do
- IssueObserver.current_user = issue.author
issue.close
milestone.issues << issue
milestone.percent_complete.should == 100
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
new file mode 100644
index 00000000000..716fd81c91b
--- /dev/null
+++ b/spec/models/project_snippet_spec.rb
@@ -0,0 +1,30 @@
+# == Schema Information
+#
+# Table name: snippets
+#
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# author_id :integer not null
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# file_name :string(255)
+# expires_at :datetime
+#
+
+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 04b4ce1763e..2e3870b1b65 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -36,7 +36,7 @@ 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(: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) }
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index e4d1934829f..52355c38f0c 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -17,19 +17,16 @@ 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 380bbe7351f..4dd2048ccad 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -41,6 +41,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) }
@@ -105,11 +106,33 @@ describe User do
ActiveRecord::Base.observers.enable(:user_observer)
@user = create :user
@project = create :project, namespace: @user.namespace
+ @project_2 = create :project # Grant MASTER access to the user
+ @project_3 = create :project # Grant DEVELOPER access to the user
+
+ UsersProject.add_users_into_projects(
+ [@project_2.id], [@user.id], UsersProject::MASTER
+ )
+ UsersProject.add_users_into_projects(
+ [@project_3.id], [@user.id], UsersProject::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) }
+
+ # master_projects doesn't check creator/namespace.
+ # In real case the users_projects relation will certainly be assigned
+ # when the project is created.
+ it { @user.master_projects.should_not include(@project) }
+ it { @user.master_projects.should include(@project_2) }
+ it { @user.master_projects.should_not include(@project_3) }
end
describe 'groups' do
@@ -125,6 +148,23 @@ describe User do
it { @user.owned_groups.should == [@group] }
end
+ describe 'teams' do
+ before do
+ ActiveRecord::Base.observers.enable(:user_observer)
+ @admin = create :user, admin: true
+ @user1 = create :user
+ @user2 = create :user
+ @team = create :user_team, owner: @user1
+ end
+
+ it { @admin.authorized_teams.should == [@team] }
+ it { @user1.authorized_teams.should == [@team] }
+ it { @user2.authorized_teams.should be_empty }
+ it { @admin.should be_can(:manage_user_team, @team) }
+ it { @user1.should be_can(:manage_user_team, @team) }
+ it { @user2.should_not be_can(:manage_user_team, @team) }
+ end
+
describe 'namespaced' do
before do
ActiveRecord::Base.observers.enable(:user_observer)
@@ -178,4 +218,22 @@ describe User do
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 == true
+ user.can_create_team.should == true
+ end
+ end
+
+ describe 'with defaults' do
+ let(:user) { User.new.with_defaults }
+ it "should apply defaults to user" do
+ user.projects_limit.should == 42
+ user.can_create_group.should == false
+ user.can_create_team.should == false
+ end
+ end
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 78d55a7b4ed..11296aea73e 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -7,7 +7,7 @@ describe API::API do
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!(: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) }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index de0631d5b70..a6612af83eb 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -10,7 +10,7 @@ describe API::API do
let(:admin) { create(:admin) }
let!(:project) { create(:project_with_code, creator_id: user.id) }
let!(:hook) { create(:project_hook, project: project, url: "http://example.com") }
- 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) }
@@ -173,6 +173,29 @@ describe API::API do
end
end
+ 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_event['action_name'].should == 'joined'
+ json_event['project_id'].to_i.should == project.id
+ end
+
+ 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
+
+ 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
+
describe "GET /projects/:id/members" do
it "should return project team members" do
get api("/projects/#{project.id}/members", user)
@@ -572,4 +595,71 @@ describe API::API do
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
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 31176316af2..13e6627840e 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -111,6 +111,29 @@ describe API::API do
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/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)
diff --git a/spec/requests/api/user_teams_spec.rb b/spec/requests/api/user_teams_spec.rb
new file mode 100644
index 00000000000..68f05b11a83
--- /dev/null
+++ b/spec/requests/api/user_teams_spec.rb
@@ -0,0 +1,360 @@
+require 'spec_helper'
+
+describe API::API do
+ include ApiHelpers
+
+ # Create test objects
+ 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(:user_team1) { create(:user_team, owner: user1) }
+ let(:user_team2) { create(:user_team, owner: user2) }
+ let!(:project1) { create(:project, creator_id: admin.id) }
+ let!(:project2) { create(:project, creator_id: admin.id) }
+
+
+ before {
+ # Add members to teams
+ user_team1.add_member(user1, UsersProject::MASTER, false)
+ user_team2.add_member(user2, UsersProject::MASTER, false)
+
+ # Add projects to teams
+ user_team1.assign_to_projects([project1.id], UsersProject::MASTER)
+ user_team2.assign_to_projects([project2.id], UsersProject::MASTER)
+
+ }
+
+ describe "GET /user_teams" do
+ context "when unauthenticated" do
+ it "should return authentication error" do
+ get api("/user_teams")
+ response.status.should == 401
+ end
+ end
+
+ context "when authenticated as user" do
+ it "normal user: should return an array of user_teams of user1" do
+ get api("/user_teams", user1)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.length.should == 1
+ json_response.first['name'].should == user_team1.name
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "admin: should return an array of all user_teams" do
+ get api("/user_teams", admin)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.length.should == 2
+ end
+ end
+ end
+
+ describe "GET /user_teams/:id" do
+ context "when authenticated as user" do
+ it "should return one of user1's user_teams" do
+ get api("/user_teams/#{user_team1.id}", user1)
+ response.status.should == 200
+ json_response['name'] == user_team1.name
+ end
+
+ it "should not return a non existing team" do
+ get api("/user_teams/1328", user1)
+ response.status.should == 404
+ end
+
+ it "should not return a user_team not attached to user1" do
+ get api("/user_teams/#{user_team2.id}", user1)
+ response.status.should == 404
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "should return any existing user_team" do
+ get api("/user_teams/#{user_team2.id}", admin)
+ response.status.should == 200
+ json_response['name'].should == user_team2.name
+ end
+
+ it "should not return a non existing user_team" do
+ get api("/user_teams/1328", admin)
+ response.status.should == 404
+ end
+ end
+ end
+
+ describe "POST /user_teams" do
+ context "when authenticated as user" do
+ it "should not create user_team" do
+ count_before=UserTeam.count
+ post api("/user_teams", user1), attributes_for(:user_team)
+ response.status.should == 403
+ UserTeam.count.should == count_before
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "should create user_team" do
+ count_before=UserTeam.count
+ post api("/user_teams", admin), attributes_for(:user_team)
+ response.status.should == 201
+ UserTeam.count.should == count_before + 1
+ end
+
+ it "should not create user_team, duplicate" do
+ post api("/user_teams", admin), {:name => "Duplicate Test", :path => user_team2.path}
+ response.status.should == 404
+ end
+
+ it "should return 400 bad request error if name not given" do
+ post api("/user_teams", admin), {:path => user_team2.path}
+ response.status.should == 400
+ end
+
+ it "should return 400 bad request error if path not given" do
+ post api("/user_teams", admin), {:name => 'test'}
+ response.status.should == 400
+ end
+ end
+ end
+
+ # Members
+
+ describe "GET /user_teams/:id/members" do
+ context "when authenticated as user" do
+ it "should return user1 as member of user1's user_teams" do
+ get api("/user_teams/#{user_team1.id}/members", user1)
+ response.status.should == 200
+ json_response.first['name'].should == user1.name
+ json_response.first['access_level'].should == UsersProject::MASTER
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "should return member of any existing user_team" do
+ get api("/user_teams/#{user_team2.id}/members", admin)
+ response.status.should == 200
+ json_response.first['name'].should == user2.name
+ json_response.first['access_level'].should == UsersProject::MASTER
+ end
+ end
+ end
+
+ describe "POST /user_teams/:id/members" do
+ context "when authenticated as user" do
+ it "should not add user2 as member of user_team1" do
+ post api("/user_teams/#{user_team1.id}/members", user1), user_id: user2.id, access_level: UsersProject::MASTER
+ response.status.should == 403
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "should return ok and add new member" do
+ count_before=user_team1.user_team_user_relationships.count
+ post api("/user_teams/#{user_team1.id}/members", admin), user_id: user2.id, access_level: UsersProject::MASTER
+ response.status.should == 201
+ json_response['name'].should == user2.name
+ json_response['access_level'].should == UsersProject::MASTER
+ user_team1.user_team_user_relationships.count.should == count_before + 1
+ end
+ it "should return ok if member already exists" do
+ post api("/user_teams/#{user_team2.id}/members", admin), user_id: user2.id, access_level: UsersProject::MASTER
+ response.status.should == 409
+ end
+ it "should return a 400 error when user id is not given" do
+ post api("/user_teams/#{user_team2.id}/members", admin), access_level: UsersProject::MASTER
+ response.status.should == 400
+ end
+ it "should return a 400 error when access level is not given" do
+ post api("/user_teams/#{user_team2.id}/members", admin), user_id: user2.id
+ response.status.should == 400
+ end
+
+ it "should return a 422 error when access level is not known" do
+ post api("/user_teams/#{user_team2.id}/members", admin), user_id: user1.id, access_level: 1234
+ response.status.should == 422
+ end
+
+ end
+ end
+
+ # Get single member
+ describe "GET /user_teams/:id/members/:user_id" do
+ context "when authenticated as member" do
+ it "should show user1's membership of user_team1" do
+ get api("/user_teams/#{user_team1.id}/members/#{user1.id}", user1)
+ response.status.should == 200
+ json_response['name'].should == user1.name
+ json_response['access_level'].should == UsersProject::MASTER
+ end
+ it "should show that user2 is not member of user_team1" do
+ get api("/user_teams/#{user_team1.id}/members/#{user2.id}", user1)
+ response.status.should == 404
+ end
+ end
+
+ context "when authenticated as non-member" do
+ it "should not show user1's membership of user_team1" do
+ get api("/user_teams/#{user_team1.id}/members/#{user1.id}", user2)
+ response.status.should == 404
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "should show user1's membership of user_team1" do
+ get api("/user_teams/#{user_team1.id}/members/#{user1.id}", admin)
+ response.status.should == 200
+ json_response['name'].should == user1.name
+ json_response['access_level'].should == UsersProject::MASTER
+ end
+ it "should return a 404 error when user id is not known" do
+ get api("/user_teams/#{user_team2.id}/members/1328", admin)
+ response.status.should == 404
+ end
+ end
+ end
+
+ describe "DELETE /user_teams/:id/members/:user_id" do
+ context "when authenticated as user" do
+ it "should not delete user1's membership of user_team1" do
+ delete api("/user_teams/#{user_team1.id}/members/#{user1.id}", user1)
+ response.status.should == 403
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "should delete user1's membership of user_team1" do
+ count_before=user_team1.user_team_user_relationships.count
+ delete api("/user_teams/#{user_team1.id}/members/#{user1.id}", admin)
+ response.status.should == 200
+ user_team1.user_team_user_relationships.count.should == count_before - 1
+ end
+ it "should return a 404 error when user id is not known" do
+ delete api("/user_teams/#{user_team2.id}/members/1328", admin)
+ response.status.should == 404
+ end
+ end
+ end
+
+ # Projects
+
+ describe "GET /user_teams/:id/projects" do
+ context "when authenticated as user" do
+ it "should return project1 as assigned to user_team1 as member user1" do
+ get api("/user_teams/#{user_team1.id}/projects", user1)
+ response.status.should == 200
+ json_response.first['name'].should == project1.name
+ json_response.length.should == user_team1.user_team_project_relationships.count
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "should return project2 as assigned to user_team2 as non-member, but admin" do
+ get api("/user_teams/#{user_team2.id}/projects", admin)
+ response.status.should == 200
+ json_response.first['name'].should == project2.name
+ json_response.first['greatest_access_level'].should == UsersProject::MASTER
+ end
+ end
+ end
+
+ describe "POST /user_teams/:id/projects" do
+ context "when authenticated as admin" do
+ it "should return ok and add new project" do
+ count_before=user_team1.user_team_project_relationships.count
+ post api("/user_teams/#{user_team1.id}/projects", admin),
+ project_id: project2.id,
+ greatest_access_level: UsersProject::MASTER
+ response.status.should == 201
+ json_response['name'].should == project2.name
+ json_response['greatest_access_level'].should == UsersProject::MASTER
+ user_team1.user_team_project_relationships.count.should == count_before + 1
+ end
+ it "should return ok if project already exists" do
+ post api("/user_teams/#{user_team2.id}/projects", admin),
+ project_id: project2.id,
+ greatest_access_level: UsersProject::MASTER
+ response.status.should == 409
+ end
+ it "should return a 400 error when project id is not given" do
+ post api("/user_teams/#{user_team2.id}/projects", admin), greatest_access_level: UsersProject::MASTER
+ response.status.should == 400
+ end
+ it "should return a 400 error when access level is not given" do
+ post api("/user_teams/#{user_team2.id}/projects", admin), project_id: project2.id
+ response.status.should == 400
+ end
+
+ it "should return a 422 error when access level is not known" do
+ post api("/user_teams/#{user_team2.id}/projects", admin),
+ project_id: project2.id,
+ greatest_access_level: 1234
+ response.status.should == 422
+ end
+
+ end
+ end
+
+
+ describe "GET /user_teams/:id/projects/:project_id" do
+ context "when authenticated as member" do
+ it "should show project1's assignment to user_team1" do
+ get api("/user_teams/#{user_team1.id}/projects/#{project1.id}", user1)
+ response.status.should == 200
+ json_response['name'].should == project1.name
+ json_response['greatest_access_level'].should == UsersProject::MASTER
+ end
+ it "should show project2's is not assigned to user_team1" do
+ get api("/user_teams/#{user_team1.id}/projects/#{project2.id}", user1)
+ response.status.should == 404
+ end
+ end
+
+ context "when authenticated as non-member" do
+ it "should not show project1's assignment to user_team1" do
+ get api("/user_teams/#{user_team1.id}/projects/#{project1.id}", user2)
+ response.status.should == 404
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "should show project1's assignment to user_team1" do
+ get api("/user_teams/#{user_team1.id}/projects/#{project1.id}", admin)
+ response.status.should == 200
+ json_response['name'].should == project1.name
+ json_response['greatest_access_level'].should == UsersProject::MASTER
+ end
+ it "should return a 404 error when project id is not known" do
+ get api("/user_teams/#{user_team2.id}/projects/1328", admin)
+ response.status.should == 404
+ end
+ end
+ end
+
+ describe "DELETE /user_teams/:id/projects/:project_id" do
+ context "when authenticated as user" do
+ it "should not delete project1's assignment to user_team2" do
+ delete api("/user_teams/#{user_team2.id}/projects/#{project1.id}", user1)
+ response.status.should == 403
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "should delete project1's assignment to user_team1" do
+ count_before=user_team1.user_team_project_relationships.count
+ delete api("/user_teams/#{user_team1.id}/projects/#{project1.id}", admin)
+ response.status.should == 200
+ user_team1.user_team_project_relationships.count.should == count_before - 1
+ end
+ it "should return a 404 error when project id is not known" do
+ delete api("/user_teams/#{user_team2.id}/projects/1328", admin)
+ response.status.should == 404
+ end
+ end
+ end
+
+end
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index c14fff5109b..36b546fb077 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -122,10 +122,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/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index f20a1ca51a4..0b6528bfc81 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -201,7 +201,11 @@ describe RefsController, "routing" do
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/feature%2345/logs_tree").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'feature#45')
+ get("/gitlabhq/refs/feature%2B45/logs_tree").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'feature+45')
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("/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'feature#45', path: 'foo/bar/baz')
+ get("/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'feature+45', path: 'foo/bar/baz')
get("/gitlab/gitlabhq/refs/stable/logs_tree/files.scss").should route_to('refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'files.scss')
end
end
@@ -260,11 +264,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("/gitlabhq/snippets/1/raw").should route_to('projects/snippets#raw', project_id: 'gitlabhq', id: '1')
end
- it_behaves_like "RESTful project resources" do
- let(:controller) { 'snippets' }
+ it "to #index" do
+ get("/gitlabhq/snippets").should route_to("projects/snippets#index", project_id: 'gitlabhq')
+ end
+
+ it "to #create" do
+ post("/gitlabhq/snippets").should route_to("projects/snippets#create", project_id: 'gitlabhq')
+ end
+
+ it "to #new" do
+ get("/gitlabhq/snippets/new").should route_to("projects/snippets#new", project_id: 'gitlabhq')
+ end
+
+ it "to #edit" do
+ get("/gitlabhq/snippets/1/edit").should route_to("projects/snippets#edit", project_id: 'gitlabhq', id: '1')
+ end
+
+ it "to #show" do
+ get("/gitlabhq/snippets/1").should route_to("projects/snippets#show", project_id: 'gitlabhq', id: '1')
+ end
+
+ it "to #update" do
+ put("/gitlabhq/snippets/1").should route_to("projects/snippets#update", project_id: 'gitlabhq', id: '1')
+ end
+
+ it "to #destroy" do
+ delete("/gitlabhq/snippets/1").should route_to("projects/snippets#destroy", project_id: 'gitlabhq', id: '1')
end
end
@@ -422,9 +450,15 @@ describe CompareController, "routing" do
end
end
-describe GraphController, "routing" do
+describe NetworkController, "routing" do
+ it "to #show" do
+ get("/gitlabhq/network/master").should route_to('network#show', project_id: 'gitlabhq', id: 'master')
+ get("/gitlabhq/network/master.json").should route_to('network#show', project_id: 'gitlabhq', id: 'master', format: "json")
+ end
+end
+
+describe GraphsController, "routing" do
it "to #show" do
- get("/gitlabhq/graph/master").should route_to('graph#show', project_id: 'gitlabhq', id: 'master')
- get("/gitlabhq/graph/master.json").should route_to('graph#show', project_id: 'gitlabhq', id: 'master', format: "json")
+ get("/gitlabhq/graphs/master").should route_to('graphs#show', project_id: 'gitlabhq', id: 'master')
end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 049cfeaab01..aa3952f74b6 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -8,23 +8,62 @@ describe SearchController, "routing" do
end
# gitlab_api /api API::API
-# resque /info/resque Resque::Server
# /:path Grack
describe "Mounted Apps", "routing" do
it "to API" do
get("/api").should be_routable
end
- it "to Resque" do
- pending
- get("/info/resque").should be_routable
- end
-
it "to Grack" do
get("/gitlabhq.git").should be_routable
end
end
+# 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
+
# help GET /help(.:format) help#index
# help_permissions GET /help/permissions(.:format) help#permissions
# help_workflow GET /help/workflow(.:format) help#workflow
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 21e4202f4ee..76501482303 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -19,7 +19,7 @@ describe NotificationService do
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) }
+ let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced') }
before do
build_team(note.project)
@@ -30,6 +30,7 @@ describe NotificationService 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)
@@ -235,9 +236,11 @@ describe NotificationService do
@u_watcher = create(:user, notification_level: Notification::N_WATCH)
@u_participating = create(:user, notification_level: Notification::N_PARTICIPATING)
@u_disabled = create(:user, notification_level: Notification::N_DISABLED)
+ @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_WATCH)
project.team << [@u_watcher, :master]
project.team << [@u_participating, :master]
project.team << [@u_disabled, :master]
+ project.team << [@u_mentioned, :master]
end
end