summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-08-04 16:42:36 +0200
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-08-04 16:42:36 +0200
commitb48b07044b919c07de34434aea7cdba13d7c38a6 (patch)
tree5a6d9d8de1048280a595825aa5f47464455ad670
parentc17f5d06aa4a55a8446928ea6b690ae8e09ce237 (diff)
parent326b827ce39f998ce75f58e9f649e6b50623f1aa (diff)
downloadgitlab-ce-b48b07044b919c07de34434aea7cdba13d7c38a6.tar.gz
Merge branch 'master' into drop-satellites
Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
-rw-r--r--CHANGELOG62
-rw-r--r--Gemfile13
-rw-r--r--Gemfile.lock14
-rw-r--r--README.md70
-rw-r--r--app/assets/images/auth_buttons/bitbucket_64.png (renamed from app/assets/images/authbuttons/bitbucket_64.png)bin2163 -> 2163 bytes
-rw-r--r--app/assets/images/auth_buttons/github_64.pngbin0 -> 2625 bytes
-rw-r--r--app/assets/images/auth_buttons/gitlab_64.pngbin0 -> 2849 bytes
-rw-r--r--app/assets/images/auth_buttons/google_64.png (renamed from app/assets/images/authbuttons/google_64.png)bin5281 -> 5281 bytes
-rw-r--r--app/assets/images/auth_buttons/twitter_64.png (renamed from app/assets/images/authbuttons/twitter_64.png)bin4835 -> 4835 bytes
-rw-r--r--app/assets/images/authbuttons/github_64.pngbin4196 -> 0 bytes
-rw-r--r--app/assets/images/authbuttons/gitlab_64.pngbin6559 -> 0 bytes
-rw-r--r--app/assets/javascripts/application.js.coffee7
-rw-r--r--app/assets/javascripts/diff.js.coffee4
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee5
-rw-r--r--app/assets/javascripts/line_highlighter.js.coffee2
-rw-r--r--app/assets/javascripts/merge_request.js.coffee12
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee10
-rw-r--r--app/assets/javascripts/notes.js.coffee3
-rw-r--r--app/assets/stylesheets/base/mixins.scss2
-rw-r--r--app/assets/stylesheets/generic/common.scss2
-rw-r--r--app/assets/stylesheets/generic/typography.scss4
-rw-r--r--app/assets/stylesheets/pages/diff.scss11
-rw-r--r--app/assets/stylesheets/pages/notes.scss4
-rw-r--r--app/assets/stylesheets/pages/projects.scss9
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/groups/application_controller.rb6
-rw-r--r--app/controllers/groups/group_members_controller.rb4
-rw-r--r--app/controllers/groups_controller.rb6
-rw-r--r--app/controllers/import/bitbucket_controller.rb1
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb5
-rw-r--r--app/controllers/projects/milestones_controller.rb7
-rw-r--r--app/controllers/projects/network_controller.rb4
-rw-r--r--app/controllers/projects/refs_controller.rb6
-rw-r--r--app/controllers/projects/tree_controller.rb4
-rw-r--r--app/controllers/projects_controller.rb10
-rw-r--r--app/controllers/sessions_controller.rb2
-rw-r--r--app/helpers/auth_helper.rb50
-rw-r--r--app/helpers/blob_helper.rb8
-rw-r--r--app/helpers/emails_helper.rb4
-rw-r--r--app/helpers/oauth_helper.rb34
-rw-r--r--app/helpers/profile_helper.rb13
-rw-r--r--app/helpers/projects_helper.rb67
-rw-r--r--app/models/ability.rb63
-rw-r--r--app/models/application_setting.rb5
-rw-r--r--app/models/audit_event.rb14
-rw-r--r--app/models/concerns/issuable.rb10
-rw-r--r--app/models/group.rb6
-rw-r--r--app/models/project.rb18
-rw-r--r--app/models/project_services/gitlab_ci_service.rb8
-rw-r--r--app/models/repository.rb58
-rw-r--r--app/models/security_event.rb14
-rw-r--r--app/models/user.rb5
-rw-r--r--app/services/git_push_service.rb2
-rw-r--r--app/services/git_tag_push_service.rb6
-rw-r--r--app/services/issuable_base_service.rb6
-rw-r--r--app/services/issues/base_service.rb4
-rw-r--r--app/services/merge_requests/base_service.rb6
-rw-r--r--app/services/projects/create_service.rb2
-rw-r--r--app/views/admin/groups/show.html.haml34
-rw-r--r--app/views/admin/identities/_form.html.haml3
-rw-r--r--app/views/admin/identities/_identity.html.haml2
-rw-r--r--app/views/devise/sessions/_new_base.html.haml2
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml2
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml8
-rw-r--r--app/views/devise/shared/_signin_box.html.haml2
-rw-r--r--app/views/events/_event.html.haml15
-rw-r--r--app/views/explore/projects/_project.html.haml2
-rw-r--r--app/views/groups/group_members/_group_member.html.haml2
-rw-r--r--app/views/groups/group_members/index.html.haml2
-rw-r--r--app/views/layouts/notify.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml14
-rw-r--r--app/views/profiles/keys/_form.html.haml7
-rw-r--r--app/views/profiles/two_factor_auths/new.html.haml2
-rw-r--r--app/views/projects/blob/diff.html.haml2
-rw-r--r--app/views/projects/diffs/_text_file.html.haml3
-rw-r--r--app/views/projects/diffs/_warning.html.haml2
-rw-r--r--app/views/projects/issues/_issue.html.haml73
-rw-r--r--app/views/projects/merge_requests/_show.html.haml16
-rw-r--r--app/views/projects/merge_requests/branch_from.js.haml1
-rw-r--r--app/views/projects/merge_requests/branch_to.js.haml1
-rw-r--r--app/views/projects/merge_requests/widget/open/_missing_branch.html.haml6
-rw-r--r--app/views/projects/milestones/_milestone.html.haml4
-rw-r--r--app/views/projects/milestones/show.html.haml3
-rw-r--r--app/views/projects/network/show.html.haml6
-rw-r--r--app/views/projects/notes/_edit_form.html.haml5
-rw-r--r--app/views/projects/notes/_form.html.haml14
-rw-r--r--app/views/projects/notes/_hints.html.haml9
-rw-r--r--app/views/projects/notes/_note.html.haml7
-rw-r--r--app/views/projects/refs/logs_tree.js.haml8
-rw-r--r--app/views/projects/show.html.haml27
-rw-r--r--app/views/projects/tree/_tree.html.haml2
-rw-r--r--app/views/projects/wikis/show.html.haml6
-rw-r--r--app/views/shared/issuable/_context.html.haml4
-rw-r--r--app/views/shared/issuable/_form.html.haml4
-rw-r--r--app/workers/project_cache_worker.rb15
-rw-r--r--app/workers/repository_import_worker.rb2
-rw-r--r--config/application.rb1
-rw-r--r--config/gitlab.yml.example28
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--config/initializers/7_omniauth.rb1
-rw-r--r--config/initializers/doorkeeper.rb3
-rw-r--r--config/initializers/redis-store-fix-expiry.rb44
-rw-r--r--config/routes.rb2
-rw-r--r--db/fixtures/development/04_project.rb35
-rw-r--r--db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb6
-rw-r--r--db/migrate/20150717130904_add_commits_count_to_project.rb5
-rw-r--r--db/schema.rb3
-rw-r--r--doc/api/merge_requests.md15
-rw-r--r--doc/api/users.md132
-rw-r--r--doc/gitlab-basics/README.md10
-rw-r--r--doc/gitlab-basics/add-file.md31
-rw-r--r--doc/gitlab-basics/add-merge-request.md42
-rw-r--r--doc/gitlab-basics/basic-git-commands.md32
-rw-r--r--doc/gitlab-basics/basicsimages/button-create-mr.pngbin0 -> 6154 bytes
-rw-r--r--doc/gitlab-basics/command-line-commands.md30
-rw-r--r--doc/gitlab-basics/create-branch.md39
-rw-r--r--doc/gitlab-basics/create-group.md2
-rw-r--r--doc/gitlab-basics/create-project.md8
-rw-r--r--doc/gitlab-basics/create-your-ssh-keys.md20
-rw-r--r--doc/gitlab-basics/fork-project.md19
-rw-r--r--doc/gitlab-basics/start-using-git.md36
-rw-r--r--doc/integration/gitlab_actions.pngbin17321 -> 0 bytes
-rw-r--r--doc/integration/gitlab_buttons_in_gmail.md28
-rw-r--r--doc/integration/omniauth.md2
-rw-r--r--doc/integration/twitter.md12
-rw-r--r--doc/permissions/permissions.md4
-rw-r--r--doc/profile/two_factor_authentication.md5
-rw-r--r--doc/raketasks/README.md1
-rw-r--r--doc/raketasks/backup_restore.md52
-rw-r--r--doc/release/monthly.md11
-rw-r--r--doc/ssh/README.md3
-rw-r--r--doc/workflow/README.md1
-rw-r--r--doc/workflow/add-user/add-user.md25
-rw-r--r--doc/workflow/add-user/images/add-members.pngbin0 -> 2361 bytes
-rw-r--r--doc/workflow/add-user/images/members.pngbin0 -> 8295 bytes
-rw-r--r--doc/workflow/add-user/images/new-member.pngbin0 -> 12038 bytes
-rw-r--r--doc/workflow/add-user/images/select-project.pngbin0 -> 4042 bytes
-rw-r--r--doc/workflow/gitlab_flow.md8
-rw-r--r--doc/workflow/importing/README.md5
-rw-r--r--doc/workflow/labels.md4
-rw-r--r--docker/Dockerfile4
-rw-r--r--docker/README.md2
-rwxr-xr-xdocker/assets/wrapper5
-rw-r--r--features/project/issues/milestones.feature4
-rw-r--r--features/project/network_graph.feature5
-rw-r--r--features/project/source/browse_files.feature7
-rw-r--r--features/steps/admin/users.rb9
-rw-r--r--features/steps/project/issues/milestones.rb8
-rw-r--r--features/steps/project/network_graph.rb14
-rw-r--r--features/steps/project/source/browse_files.rb17
-rw-r--r--lib/api/entities.rb15
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/users.rb123
-rw-r--r--lib/backup/database.rb6
-rw-r--r--lib/backup/manager.rb8
-rw-r--r--lib/backup/repository.rb5
-rw-r--r--lib/backup/uploads.rb6
-rw-r--r--lib/gitlab/backend/grack_auth.rb11
-rw-r--r--lib/gitlab/backend/shell_env.rb11
-rw-r--r--lib/gitlab/bitbucket_import/client.rb21
-rw-r--r--lib/gitlab/google_code_import/importer.rb2
-rw-r--r--lib/gitlab/markdown/relative_link_filter.rb14
-rw-r--r--lib/gitlab/o_auth/provider.rb28
-rw-r--r--lib/redcarpet/render/gitlab_html.rb8
-rw-r--r--lib/repository_cache.rb8
-rw-r--r--lib/rouge/formatters/html_gitlab.rb176
-rwxr-xr-xlib/support/init.d/gitlab2
-rw-r--r--lib/support/nginx/gitlab25
-rw-r--r--lib/support/nginx/gitlab-ssl25
-rw-r--r--lib/tasks/gitlab/check.rake8
-rw-r--r--lib/tasks/gitlab/import.rake4
-rw-r--r--lib/tasks/gitlab/mail_google_schema_whitelisting.rake73
-rw-r--r--lib/tasks/gitlab/update_commit_count.rake20
-rw-r--r--lib/unfold_form.rb1
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb28
-rw-r--r--spec/controllers/projects/tree_controller_spec.rb (renamed from spec/controllers/tree_controller_spec.rb)29
-rw-r--r--spec/controllers/users_controller_spec.rb35
-rw-r--r--spec/factories/projects.rb3
-rw-r--r--spec/features/markdown_spec.rb427
-rw-r--r--spec/features/security/admin_access_spec.rb (renamed from spec/features/admin/security_spec.rb)2
-rw-r--r--spec/features/security/dashboard_access_spec.rb2
-rw-r--r--spec/features/security/group/group_access_spec.rb98
-rw-r--r--spec/features/security/group/internal_group_access_spec.rb82
-rw-r--r--spec/features/security/group/mixed_group_access_spec.rb83
-rw-r--r--spec/features/security/group/public_group_access_spec.rb82
-rw-r--r--spec/features/security/group_access_spec.rb284
-rw-r--r--spec/features/security/profile_access_spec.rb14
-rw-r--r--spec/features/security/project/internal_access_spec.rb2
-rw-r--r--spec/features/security/project/private_access_spec.rb2
-rw-r--r--spec/features/security/project/public_access_spec.rb3
-rw-r--r--spec/fixtures/GoogleCodeProjectHosting.json5
-rw-r--r--spec/fixtures/markdown.md.erb13
-rw-r--r--spec/helpers/auth_helper_spec.rb20
-rw-r--r--spec/helpers/blob_helper_spec.rb18
-rw-r--r--spec/helpers/oauth_helper_spec.rb20
-rw-r--r--spec/helpers/projects_helper_spec.rb20
-rw-r--r--spec/javascripts/fixtures/line_highlighter.html.haml4
-rw-r--r--spec/javascripts/line_highlighter_spec.js.coffee8
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js.coffee6
-rw-r--r--spec/lib/gitlab/google_code_import/importer_spec.rb1
-rw-r--r--spec/lib/gitlab/markdown/relative_link_filter_spec.rb16
-rw-r--r--spec/models/application_setting_spec.rb5
-rw-r--r--spec/models/project_services/gitlab_ci_service_spec.rb27
-rw-r--r--spec/models/project_spec.rb21
-rw-r--r--spec/models/user_spec.rb13
-rw-r--r--spec/requests/api/branches_spec.rb5
-rw-r--r--spec/requests/api/merge_requests_spec.rb6
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/services_spec.rb14
-rw-r--r--spec/requests/api/users_spec.rb171
-rw-r--r--spec/services/projects/create_service_spec.rb12
-rw-r--r--spec/services/projects/fork_service_spec.rb2
-rw-r--r--spec/support/markdown_feature.rb106
-rw-r--r--spec/support/matchers.rb66
-rw-r--r--spec/support/matchers/access_matchers.rb54
-rw-r--r--spec/support/matchers/include_module.rb13
-rw-r--r--spec/support/matchers/is_within.rb9
-rw-r--r--spec/support/matchers/markdown_matchers.rb156
-rw-r--r--spec/support/test_env.rb19
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb52
220 files changed, 2983 insertions, 1477 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 12564b3bb56..8a24bf412d2 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,59 @@
Please view this file on the master branch, on stable branches it's out of date.
-v 7.13.0 (unreleased)
+v 7.14.0 (unreleased)
+ - Fix multi-line syntax highlighting (Stan Hu)
+ - Fix network graph when branch name has single quotes (Stan Hu)
+ - Upgrade gitlab_git to version 7.2.6 to fix Error 500 when creating network graphs (Stan Hu)
+ - Add support for Unicode filenames in relative links (Hiroyuki Sato)
+ - Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki)
+ - Fix commit data retrieval when branch name has single quotes (Stan Hu)
+ - Check that project was actually created rather than just validated in import:repos task (Stan Hu)
+ - Fix full screen mode for snippet comments (Daniel Gerhardt)
+ - Fix 404 error in files view after deleting the last file in a repository (Stan Hu)
+ - Fix the "Reload with full diff" URL button (Stan Hu)
+ - Fix label read access for unauthenticated users (Daniel Gerhardt)
+ - Fix access to disabled features for unauthenticated users (Daniel Gerhardt)
+ - Fix OAuth provider bug where GitLab would not go return to the redirect_uri after sign-in (Stan Hu)
+ - Fix file upload dialog for comment editing (Daniel Gerhardt)
+ - Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu)
+ - Expire Rails cache entries after two weeks to prevent endless Redis growth
+ - Add support for destroying project milestones (Stan Hu)
+ - Add fetch command to the MR page.
+ - Allow custom backup archive permissions
+ - Add fetch command to the MR page
+ - Add project star and fork count, group avatar URL and user/group web URL attributes to API
+ - Fix bug causing Bitbucket importer to crash when OAuth application had been removed.
+ - Add fetch command to the MR page.
+ - Add ability to manage user email addresses via the API.
+ - Show buttons to add license, changelog and contribution guide if they're missing.
+ - Disabled autocapitalize and autocorrect on login field (Daryl Chan)
+ - Mention group and project name in creation, update and deletion notices (Achilleas Pipinellis)
+ - Remove redis-store TTL monkey patch
+
+v 7.13.2
+ - Fix randomly failed spec
+ - Create project services on Project creation
+ - Add admin_merge_request ability to Developer level and up
+ - Fix Error 500 when browsing projects with no HEAD (Stan Hu)
+ - Fix labels / assignee / milestone for the merge requests when issues are disabled
+ - Show the first tab automatically on MergeRequests#new
+ - Add rake task 'gitlab:update_commit_count' (Daniel Gerhardt)
+ - Fix Gmail Actions
+
+v 7.13.1
+ - Fix: Label modifications are not reflected in existing notes and in the issue list
+ - Fix: Label not shown in the Issue list, although it's set through web interface
+ - Fix: Group/project references are linked incorrectly
+ - Improve documentation
+ - Fix of migration: Check if session_expire_delay column exists before adding the column
+ - Fix: ActionView::Template::Error
+ - Fix: "Create Merge Request" isn't always shown in event for newly pushed branch
+ - Fix bug causing "Remove source-branch" option not to work for merge requests from the same project.
+ - Render Note field hints consistently for "new" and "edit" forms
+
+v 7.13.0
+ - Remove repository graph log to fix slow cache updates after push event (Stan Hu)
+ - Return comments in created order in merge request API (Stan Hu)
- Only enable HSTS header for HTTPS and port 443 (Stan Hu)
- Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu)
- Fix redirection to home page URL for unauthorized users (Daniel Gerhardt)
@@ -46,12 +99,15 @@ v 7.13.0 (unreleased)
- Faster code search in repository and wiki. Fixes search page timeout for big repositories
- Allow administrators to disable 2FA for a specific user
- Add error message for SSH key linebreaks
+ - Store commits count in database (will populate with valid values only after first push)
+ - Rebuild cache after push to repository in background job
v 7.12.2
- Correctly show anonymous authorized applications under Profile > Applications.
- Faster automerge check and merge itself when source and target branches are in same repository
- Audit log for user authentication
- Fix transferring of project to another group using the API.
+ - Allow custom label to be set for authentication providers.
v 7.12.1
- Fix error when deleting a user who has projects (Stan Hu)
@@ -121,6 +177,7 @@ v 7.12.0
- Improve group removing logic
- Trigger create-hooks on backup restore task
- Add option to automatically link omniauth and LDAP identities
+ - Allow special character in users bio. I.e.: I <3 GitLab
v 7.11.4
- Fix missing bullets when creating lists
@@ -139,9 +196,6 @@ v 7.11.1
v 7.11.0
- Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger)
- Get editing comments to work in Chrome 43 again.
- - Allow special character in users bio. I.e.: I <3 GitLab
-
-v 7.11.0
- Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu)
- Don't show duplicate deploy keys
- Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger)
diff --git a/Gemfile b/Gemfile
index 9b7fd18ab19..1c49a603798 100644
--- a/Gemfile
+++ b/Gemfile
@@ -38,7 +38,7 @@ gem "browser", '~> 0.8.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.2.5'
+gem "gitlab_git", '~> 7.2.6'
# Ruby/Rack Git Smart-HTTP Server Handler
# GitLab fork with a lot of changes (improved thread-safety, better memory usage etc)
@@ -46,7 +46,7 @@ gem "gitlab_git", '~> 7.2.5'
gem 'gitlab-grack', '~> 2.0.2', require: 'grack'
# LDAP Auth
-# GitLab fork with several improvements to original library. For full list of changes
+# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
@@ -54,9 +54,9 @@ gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
gem 'gollum-lib', '~> 4.0.2'
# Language detection
-# GitLab fork of linguist does not require pygments/python dependency.
-# New version of original gem also dropped pygments support but it has strict
-# dependency to unstable rugged version. We have internal issue for replacing
+# GitLab fork of linguist does not require pygments/python dependency.
+# New version of original gem also dropped pygments support but it has strict
+# dependency to unstable rugged version. We have internal issue for replacing
# fork with original gem when we meet on same rugged version - https://dev.gitlab.org/gitlab/gitlabhq/issues/2052.
gem "gitlab-linguist", "~> 3.0.1", require: "linguist"
@@ -227,7 +227,7 @@ end
group :development, :test do
gem 'awesome_print'
- gem 'byebug'
+ gem 'byebug', platform: :mri
gem 'fuubar', '~> 2.0.0'
gem 'pry-rails'
@@ -272,4 +272,3 @@ end
gem "newrelic_rpm"
gem 'octokit', '3.7.0'
-gem "rugments", "~> 1.0.0.beta8"
diff --git a/Gemfile.lock b/Gemfile.lock
index 9830f3c77dc..e72b7fe4927 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -271,7 +271,7 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.1.0)
gemojione (~> 2.0)
- gitlab_git (7.2.5)
+ gitlab_git (7.2.6)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
@@ -288,7 +288,7 @@ GEM
github-markup (~> 1.3.1)
gollum-grit_adapter (~> 0.1, >= 0.1.1)
nokogiri (~> 1.6.4)
- rouge (~> 1.7.4)
+ rouge (~> 1.9)
sanitize (~> 2.1.0)
stringex (~> 2.5.1)
gon (5.0.1)
@@ -508,7 +508,7 @@ GEM
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.3.2)
- redis (3.1.0)
+ redis (3.2.1)
redis-actionpack (4.0.0)
actionpack (~> 4)
redis-rack (~> 1.5.0)
@@ -525,7 +525,7 @@ GEM
redis-actionpack (~> 4)
redis-activesupport (~> 4)
redis-store (~> 1.1.0)
- redis-store (1.1.4)
+ redis-store (1.1.6)
redis (>= 2.2)
request_store (1.0.5)
rerun (0.10.0)
@@ -536,7 +536,7 @@ GEM
netrc (~> 0.7)
rinku (1.7.3)
rotp (1.6.1)
- rouge (1.7.7)
+ rouge (1.9.1)
rqrcode (0.4.2)
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
@@ -579,7 +579,6 @@ GEM
rubyntlm (0.5.0)
rubypants (0.2.0)
rugged (0.22.2)
- rugments (1.0.0.beta8)
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
@@ -784,7 +783,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.2)
gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1)
- gitlab_git (~> 7.2.5)
+ gitlab_git (~> 7.2.6)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.1)
gollum-lib (~> 4.0.2)
@@ -836,7 +835,6 @@ DEPENDENCIES
rqrcode-rails3
rspec-rails (~> 3.3.0)
rubocop (= 0.28.0)
- rugments (~> 1.0.0.beta8)
sanitize (~> 2.0)
sass-rails (~> 4.0.5)
sdoc
diff --git a/README.md b/README.md
index f8ce5f22ec2..bd663b30f3e 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,14 @@
+# GitLab
+
+[![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master)
+[![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
+[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
+[![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
+
## Canonical source
The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible.
-# ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab
-
## Open source software to collaborate on code
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
@@ -17,21 +22,12 @@ To see how GitLab looks please see the [features page on our website](https://ab
## Editions
-There are two editions of GitLab.
-*GitLab [Community Edition](https://about.gitlab.com/features/) (CE)* is available without any costs under an MIT license.
-
-*GitLab Enterprise Edition (EE)* includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users.
-To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
-
-## Code status
-
-- [![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master) on ci.gitlab.com (master branch)
+There are two editions of GitLab:
-- [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
+- GitLab Community Edition (CE) is available freely under the MIT Expat license.
+- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/features/#compare) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
-- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
-
-- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
+Included with the GitLab Omnibus Packages is [GitLab CI](https://about.gitlab.com/gitlab-ci/) that can easily build, test and deploy code.
## Website
@@ -46,24 +42,40 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
## Requirements
-GitLab requires the following software:
-
-- Ubuntu/Debian/CentOS/RHEL
-- Ruby (MRI) 2.0 or 2.1
-- Git 1.7.10+
-- Redis 2.0+
-- MySQL or PostgreSQL
-
Please see the [requirements documentation](doc/install/requirements.md) for system requirements and more information about the supported operating systems.
## Installation
-The recommended way to install GitLab is using the provided [Omnibus packages](https://about.gitlab.com/downloads/). Compared to an installation from source, this is faster and less error prone. Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager.
+The recommended way to install GitLab is with the [Omnibus packages](https://about.gitlab.com/downloads/) on our package server.
+Compared to an installation from source, this is faster and less error prone.
+Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager.
There are various other options to install GitLab, please refer to the [installation page on the GitLab website](https://about.gitlab.com/installation/) for more information.
You can access a new installation with the login **`root`** and password **`5iveL!fe`**, after login you are required to set a unique password.
+## Install a development environment
+
+To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
+If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
+One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file:
+
+ cp config/unicorn.rb.example.development config/unicorn.rb
+
+Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
+
+## Software stack
+
+GitLab is a Ruby on Rails application that runs on the following software:
+
+- Ubuntu/Debian/CentOS/RHEL
+- Ruby (MRI) 2.0 or 2.1
+- Git 1.7.10+
+- Redis 2.0+
+- MySQL or PostgreSQL
+
+For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html).
+
## Third-party applications
There are a lot of [third-party applications integrating with GitLab](https://about.gitlab.com/applications/). These include GUI Git clients, mobile applications and API wrappers for various languages.
@@ -76,16 +88,6 @@ Since 2011 a minor or major version of GitLab is released on the 22nd of every m
For upgrading information please see our [update page](https://about.gitlab.com/update/).
-## Install a development environment
-
-To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
-If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
-One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file:
-
- cp config/unicorn.rb.example.development config/unicorn.rb
-
-Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
-
## Documentation
All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/).
diff --git a/app/assets/images/authbuttons/bitbucket_64.png b/app/assets/images/auth_buttons/bitbucket_64.png
index 4b90a57bc7d..4b90a57bc7d 100644
--- a/app/assets/images/authbuttons/bitbucket_64.png
+++ b/app/assets/images/auth_buttons/bitbucket_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/github_64.png b/app/assets/images/auth_buttons/github_64.png
new file mode 100644
index 00000000000..182a1a3f734
--- /dev/null
+++ b/app/assets/images/auth_buttons/github_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/gitlab_64.png b/app/assets/images/auth_buttons/gitlab_64.png
new file mode 100644
index 00000000000..99a40583b3a
--- /dev/null
+++ b/app/assets/images/auth_buttons/gitlab_64.png
Binary files differ
diff --git a/app/assets/images/authbuttons/google_64.png b/app/assets/images/auth_buttons/google_64.png
index fb64f8bee68..fb64f8bee68 100644
--- a/app/assets/images/authbuttons/google_64.png
+++ b/app/assets/images/auth_buttons/google_64.png
Binary files differ
diff --git a/app/assets/images/authbuttons/twitter_64.png b/app/assets/images/auth_buttons/twitter_64.png
index e3bd9169a34..e3bd9169a34 100644
--- a/app/assets/images/authbuttons/twitter_64.png
+++ b/app/assets/images/auth_buttons/twitter_64.png
Binary files differ
diff --git a/app/assets/images/authbuttons/github_64.png b/app/assets/images/authbuttons/github_64.png
deleted file mode 100644
index dc7c03d1005..00000000000
--- a/app/assets/images/authbuttons/github_64.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/authbuttons/gitlab_64.png b/app/assets/images/authbuttons/gitlab_64.png
deleted file mode 100644
index 31281a19444..00000000000
--- a/app/assets/images/authbuttons/gitlab_64.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 8b041c490d8..bb0a0c51fd4 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -164,9 +164,10 @@ $ ->
$('.account-box').hover -> $(@).toggleClass('hover')
# Commit show suppressed diff
- $(".diff-content").on "click", ".supp_diff_link", ->
- $(@).next('table').show()
- $(@).remove()
+ $(document).on 'click', '.diff-content .js-show-suppressed-diff', ->
+ $container = $(@).parent()
+ $container.next('table').show()
+ $container.remove()
$('.navbar-toggle').on 'click', ->
$('.header-content .title').toggle()
diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee
index 069f91c30e1..6d9b364cb8d 100644
--- a/app/assets/javascripts/diff.js.coffee
+++ b/app/assets/javascripts/diff.js.coffee
@@ -31,6 +31,10 @@ class @Diff
bottom: unfoldBottom
offset: offset
unfold: unfold
+ # indent is used to compensate for single space indent to fit
+ # '+' and '-' prepended to diff lines,
+ # see https://gitlab.com/gitlab-org/gitlab-ce/issues/707
+ indent: 1
$.get(link, params, (response) =>
target.parent().replaceWith(response)
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 2ab148bc296..81e73799271 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -128,7 +128,10 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
new DropzoneInput($('.wiki-form'))
- when 'snippets', 'labels', 'graphs'
+ when 'snippets'
+ shortcut_handler = new ShortcutsNavigation()
+ new ZenMode() if path[2] == 'show'
+ when 'labels', 'graphs'
shortcut_handler = new ShortcutsNavigation()
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
diff --git a/app/assets/javascripts/line_highlighter.js.coffee b/app/assets/javascripts/line_highlighter.js.coffee
index a8b3c1fa33e..e604e6025c2 100644
--- a/app/assets/javascripts/line_highlighter.js.coffee
+++ b/app/assets/javascripts/line_highlighter.js.coffee
@@ -70,7 +70,7 @@ class @LineHighlighter
@clearHighlight()
- lineNumber = $(event.target).data('line-number')
+ lineNumber = $(event.target).closest('a').data('line-number')
current = @hashToRange(@_hash)
unless current[0] && event.shiftKey
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index 7462975bd3d..b21cb7904b5 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -15,9 +15,7 @@ class @MergeRequest
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
- # `MergeRequests#new` has no tab-persisting or lazy-loading behavior
- unless @opts.action == 'new'
- new MergeRequestTabs(@opts)
+ @initTabs()
# Prevent duplicate event bindings
@disableTaskList()
@@ -29,6 +27,14 @@ class @MergeRequest
$: (selector) ->
this.$el.find(selector)
+ initTabs: ->
+ if @opts.action != 'new'
+ # `MergeRequests#new` has no tab-persisting or lazy-loading behavior
+ new MergeRequestTabs(@opts)
+ else
+ # Show the first tab (Commits)
+ $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show')
+
showAllCommits: ->
this.$('.first-commits').remove()
this.$('.all-commits').removeClass 'hide'
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index a132a0a9dcc..19a07b6a033 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -49,12 +49,6 @@ class @MergeRequestTabs
# Store the `location` object, allowing for easier stubbing in tests
@_location = location
- switch @opts.action
- when 'commits'
- @commitsLoaded = true
- when 'diffs'
- @diffsLoaded = true
-
@bindEvents()
@activateTab(@opts.action)
@@ -102,7 +96,7 @@ class @MergeRequestTabs
action = 'notes' if action == 'show'
# Remove a trailing '/commits' or '/diffs'
- new_state = @_location.pathname.replace(/\/(commits|diffs)\/?$/, '')
+ new_state = @_location.pathname.replace(/\/(commits|diffs)(\.html)?\/?$/, '')
# Append the new action if we're on a tab other than 'notes'
unless action == 'notes'
@@ -133,7 +127,7 @@ class @MergeRequestTabs
return if @diffsLoaded
@_get
- url: "#{source}.json"
+ url: "#{source}.json" + @_location.search
success: (data) =>
document.getElementById('diffs').innerHTML = data.html
@diffsLoaded = true
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 1c05a2b9fe8..0021d17d85e 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -10,7 +10,6 @@ class @Notes
constructor: (notes_url, note_ids, last_fetched_at, view) ->
@notes_url = notes_url
- @notes_url = gon.relative_url_root + @notes_url if gon.relative_url_root?
@note_ids = note_ids
@last_fetched_at = last_fetched_at
@view = view
@@ -298,7 +297,7 @@ class @Notes
note.find(".note-header").hide()
base_form = note.find(".note-edit-form")
form = base_form.clone().insertAfter(base_form)
- form.addClass('current-note-edit-form')
+ form.addClass('current-note-edit-form gfm-form')
form.find('.div-dropzone').remove()
# Show the attachment delete link
diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss
index d64e79170b9..7beef1845ef 100644
--- a/app/assets/stylesheets/base/mixins.scss
+++ b/app/assets/stylesheets/base/mixins.scss
@@ -70,7 +70,7 @@
font-family: $monospace_font;
white-space: pre;
word-wrap: normal;
- padding: 0;
+ padding: 1px 2px;
}
kbd {
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index 961ac793de2..d36530169a9 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -184,7 +184,7 @@ li.note {
}
}
-.supp_diff_link,
+.show-suppressed-diff,
.show-all-commits {
cursor: pointer;
}
diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
index 2db4213159a..34b4ee3e17e 100644
--- a/app/assets/stylesheets/generic/typography.scss
+++ b/app/assets/stylesheets/generic/typography.scss
@@ -38,6 +38,10 @@ code {
}
}
+a > code {
+ color: $link-color;
+}
+
/**
* Wiki typography
*
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index af6ea58382f..1557c243db5 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -65,6 +65,17 @@
color: #777;
}
+ .suppressed-container {
+ padding: ($padding-base-vertical + 5px) $padding-base-horizontal;
+ text-align: center;
+
+ // "Changes suppressed. Click to show." link
+ .show-suppressed-diff {
+ font-size: 110%;
+ font-weight: bold;
+ }
+ }
+
table {
width: 100%;
font-family: $monospace_font;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 4da65b28743..85c828ec1ad 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -37,7 +37,7 @@ ul.notes {
font-size: 13px;
a {
- @extend .cgray;
+ @extend .cgray;
&:hover {
text-decoration: underline;
@@ -105,6 +105,8 @@ ul.notes {
}
hr {
+ // Darken 'whitesmoke' a bit to make it more visible in note bodies
+ border-color: darken(#F5F5F5, 8%);
margin: 10px 0;
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 5f415f2d78f..21d958db80c 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -297,6 +297,15 @@ table.table.protected-branches-list tr.no-border {
ul.nav-pills { display:inline-block; }
li { display:inline; }
a { float:left; }
+
+ li.missing a {
+ color: #bbb;
+ border: 1px dashed #ccc;
+
+ &:hover {
+ background-color: #FAFAFA;
+ }
+ }
}
pre.light-well {
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 362b03e0d5e..3ce8dbc9407 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -299,14 +299,14 @@ class ApplicationController < ActionController::Base
end
def github_import_enabled?
- OauthHelper.enabled_oauth_providers.include?(:github)
+ Gitlab::OAuth::Provider.enabled?(:github)
end
def gitlab_import_enabled?
- OauthHelper.enabled_oauth_providers.include?(:gitlab)
+ Gitlab::OAuth::Provider.enabled?(:gitlab)
end
def bitbucket_import_enabled?
- OauthHelper.enabled_oauth_providers.include?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
+ Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
end
end
diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb
index 4df9d1b7533..6878d4bc07e 100644
--- a/app/controllers/groups/application_controller.rb
+++ b/app/controllers/groups/application_controller.rb
@@ -18,4 +18,10 @@ class Groups::ApplicationController < ApplicationController
return render_404
end
end
+
+ def authorize_admin_group_member!
+ unless can?(current_user, :admin_group_member, group)
+ return render_403
+ end
+ end
end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 040255f08e6..91518c44a98 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -5,6 +5,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
# Authorize
before_action :authorize_read_group!
before_action :authorize_admin_group!, except: [:index, :leave]
+ before_action :authorize_admin_group_member!, only: [:create, :resend_invite]
def index
@project = @group.projects.find(params[:project_id]) if params[:project_id]
@@ -28,6 +29,9 @@ class Groups::GroupMembersController < Groups::ApplicationController
def update
@member = @group.group_members.find(params[:id])
+
+ return render_403 unless can?(current_user, :update_group_member, @member)
+
@member.update_attributes(member_params)
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 901c1cdddcb..279c6ef0f4d 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -24,7 +24,7 @@ class GroupsController < Groups::ApplicationController
if @group.save
@group.add_owner(current_user)
- redirect_to @group, notice: 'Group was successfully created.'
+ redirect_to @group, notice: "Group '#{@group.name}' was successfully created."
else
render action: "new"
end
@@ -75,7 +75,7 @@ class GroupsController < Groups::ApplicationController
def update
if @group.update_attributes(group_params)
- redirect_to edit_group_path(@group), notice: 'Group was successfully updated.'
+ redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
else
render action: "edit"
end
@@ -84,7 +84,7 @@ class GroupsController < Groups::ApplicationController
def destroy
DestroyGroupService.new(@group, current_user).execute
- redirect_to root_path, notice: 'Group was removed.'
+ redirect_to root_path, alert: "Group '#{@group.name} was deleted."
end
protected
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index ca78a4aaa8e..af0b841f0b7 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -3,6 +3,7 @@ class Import::BitbucketController < Import::BaseController
before_action :bitbucket_auth, except: :callback
rescue_from OAuth::Error, with: :bitbucket_unauthorized
+ rescue_from Gitlab::BitbucketImport::Client::Unauthorized, with: :bitbucket_unauthorized
def callback
request_token = session.delete(:oauth_request_token)
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index fd51b380da2..523264b8ea9 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -72,10 +72,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
rescue Gitlab::OAuth::SignupDisabledError => e
- message = "Signing in using your #{oauth['provider']} account without a pre-existing GitLab account is not allowed."
+ label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
+ message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
if current_application_settings.signup_enabled?
- message << " Create a GitLab account first, and then connect it to your #{oauth['provider']} account."
+ message << " Create a GitLab account first, and then connect it to your #{label} account."
end
flash[:notice] = message
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 61689488d13..9efe9704d1e 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -64,7 +64,12 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def destroy
- return access_denied! unless can?(current_user, :admin_milestone, @milestone)
+ return access_denied! unless can?(current_user, :admin_milestone, @project)
+
+ update_params = { milestone: nil }
+ @milestone.issues.each do |issue|
+ Issues::UpdateService.new(@project, current_user, update_params).execute(issue)
+ end
@milestone.destroy
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index 06aef91cadd..b181c47baec 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -7,6 +7,10 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :authorize_download_code!
def show
+
+ @url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))
+ @commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")
+
respond_to do |format|
format.html
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index d83561cf32a..6080c849c8d 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -1,5 +1,6 @@
class Projects::RefsController < Projects::ApplicationController
include ExtractsPath
+ include TreeHelper
before_action :require_non_empty_project
before_action :assign_ref_vars
@@ -60,6 +61,11 @@ class Projects::RefsController < Projects::ApplicationController
}
end
+ if @logs.present?
+ @log_url = namespace_project_tree_url(@project.namespace, @project, tree_join(@ref, @path || '/'))
+ @more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: (@offset + @limit))
+ end
+
respond_to do |format|
format.html { render_404 }
format.js
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index b659e15f242..92e4bc16d9d 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -7,13 +7,15 @@ class Projects::TreeController < Projects::ApplicationController
before_action :authorize_download_code!
def show
+ return not_found! unless @repository.commit(@ref)
+
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
redirect_to(
namespace_project_blob_path(@project.namespace, @project,
File.join(@ref, @path))
) and return
- else
+ elsif @path.present?
return not_found!
end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index b191819a117..dafc11d0707 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,6 +1,6 @@
class ProjectsController < ApplicationController
prepend_before_filter :render_go_import, only: [:show]
- skip_before_action :authenticate_user!, only: [:show]
+ skip_before_action :authenticate_user!, only: [:show, :activity]
before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create]
@@ -24,7 +24,7 @@ class ProjectsController < ApplicationController
if @project.saved?
redirect_to(
project_path(@project),
- notice: 'Project was successfully created.'
+ notice: "Project '#{@project.name}' was successfully created."
)
else
render 'new'
@@ -36,11 +36,11 @@ class ProjectsController < ApplicationController
respond_to do |format|
if status
- flash[:notice] = 'Project was successfully updated.'
+ flash[:notice] = "Project '#{@project.name}' was successfully updated."
format.html do
redirect_to(
edit_project_path(@project),
- notice: 'Project was successfully updated.'
+ notice: "Project '#{@project.name}' was successfully updated."
)
end
format.js
@@ -100,7 +100,7 @@ class ProjectsController < ApplicationController
return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute
- flash[:alert] = 'Project deleted.'
+ flash[:alert] = "Project '#{@project.name}' was deleted."
if request.referer.include?('/admin')
redirect_to admin_namespaces_projects_path
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 89629bc0581..796cbe4c58c 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -90,7 +90,7 @@ class SessionsController < Devise::SessionsController
# Prevent alert from popping up on the first page shown after authentication.
flash[:alert] = nil
- redirect_to omniauth_authorize_path(:user, provider.to_sym)
+ redirect_to user_omniauth_authorize_path(provider.to_sym)
end
def valid_otp_attempt?(user)
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
new file mode 100644
index 00000000000..0e7a37b4cc6
--- /dev/null
+++ b/app/helpers/auth_helper.rb
@@ -0,0 +1,50 @@
+module AuthHelper
+ PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2).freeze
+ FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos'].freeze
+
+ def ldap_enabled?
+ Gitlab.config.ldap.enabled
+ end
+
+ def provider_has_icon?(name)
+ PROVIDERS_WITH_ICONS.include?(name.to_s)
+ end
+
+ def auth_providers
+ Gitlab::OAuth::Provider.providers
+ end
+
+ def label_for_provider(name)
+ Gitlab::OAuth::Provider.label_for(name)
+ end
+
+ def form_based_provider?(name)
+ FORM_BASED_PROVIDERS.any? { |pattern| pattern === name.to_s }
+ end
+
+ def form_based_providers
+ auth_providers.select { |provider| form_based_provider?(provider) }
+ end
+
+ def button_based_providers
+ auth_providers.reject { |provider| form_based_provider?(provider) }
+ end
+
+ def provider_image_tag(provider, size = 64)
+ label = label_for_provider(provider)
+
+ if provider_has_icon?(provider)
+ file_name = "#{provider.to_s.split('_').first}_#{size}.png"
+
+ image_tag(image_path("auth_buttons/#{file_name}"), alt: label, title: "Sign in with #{label}")
+ else
+ label
+ end
+ end
+
+ def auth_active?(provider)
+ current_user.identities.exists?(provider: provider.to_s)
+ end
+
+ extend self
+end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 50df3801703..77d99140c43 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -1,6 +1,6 @@
module BlobHelper
def highlight(blob_name, blob_content, nowrap: false, continue: false)
- @formatter ||= Rugments::Formatters::HTML.new(
+ @formatter ||= Rouge::Formatters::HTMLGitlab.new(
nowrap: nowrap,
cssclass: 'code highlight',
lineanchors: true,
@@ -8,11 +8,11 @@ module BlobHelper
)
begin
- @lexer ||= Rugments::Lexer.guess(filename: blob_name, source: blob_content).new
+ @lexer ||= Rouge::Lexer.guess(filename: blob_name, source: blob_content).new
result = @formatter.format(@lexer.lex(blob_content, continue: continue)).html_safe
rescue
- lexer = Rugments::Lexers::PlainText
- result = @formatter.format(lexer.lex(blob_content)).html_safe
+ @lexer = Rouge::Lexers::PlainText
+ result = @formatter.format(@lexer.lex(blob_content)).html_safe
end
result
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 128de18bc47..45788ba95ac 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -31,8 +31,8 @@ module EmailsHelper
end
def color_email_diff(diffcontent)
- formatter = Rugments::Formatters::HTML.new(cssclass: "highlight", inline_theme: :github)
- lexer = Rugments::Lexers::Diff.new
+ formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', inline_theme: 'github')
+ lexer = Rouge::Lexers::Diff
raw formatter.format(lexer.lex(diffcontent))
end
diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb
deleted file mode 100644
index 2fdca13ed40..00000000000
--- a/app/helpers/oauth_helper.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module OauthHelper
- def ldap_enabled?
- Gitlab.config.ldap.enabled
- end
-
- def default_providers
- [:twitter, :github, :gitlab, :bitbucket, :google_oauth2, :ldap]
- end
-
- def enabled_oauth_providers
- Devise.omniauth_providers
- end
-
- def enabled_social_providers
- enabled_oauth_providers.select do |name|
- [:saml, :twitter, :gitlab, :github, :bitbucket, :google_oauth2].include?(name.to_sym)
- end
- end
-
- def additional_providers
- enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')}
- end
-
- def oauth_image_tag(provider, size = 64)
- file_name = "#{provider.to_s.split('_').first}_#{size}.png"
- image_tag(image_path("authbuttons/#{file_name}"), alt: "Sign in with #{provider.to_s.titleize}")
- end
-
- def oauth_active?(provider)
- current_user.identities.exists?(provider: provider.to_s)
- end
-
- extend self
-end
diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb
deleted file mode 100644
index 780c7cd5133..00000000000
--- a/app/helpers/profile_helper.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ProfileHelper
- def show_profile_username_tab?
- current_user.can_change_username?
- end
-
- def show_profile_social_tab?
- enabled_social_providers.any?
- end
-
- def show_profile_remove_tab?
- signup_enabled?
- end
-end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 78f24dbd7ef..f5b78533896 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -131,8 +131,12 @@ module ProjectsHelper
nav_tabs << :snippets
end
+ if can?(current_user, :read_label, project)
+ nav_tabs << :labels
+ end
+
if can?(current_user, :read_milestone, project)
- nav_tabs << [:milestones, :labels]
+ nav_tabs << :milestones
end
nav_tabs.flatten
@@ -180,7 +184,43 @@ module ProjectsHelper
end
end
- def contribution_guide_url(project)
+ def add_contribution_guide_path(project)
+ if project && !project.repository.contribution_guide
+ namespace_project_new_blob_path(
+ project.namespace,
+ project,
+ project.default_branch,
+ file_name: "CONTRIBUTING.md",
+ commit_message: "Add contribution guide"
+ )
+ end
+ end
+
+ def add_changelog_path(project)
+ if project && !project.repository.changelog
+ namespace_project_new_blob_path(
+ project.namespace,
+ project,
+ project.default_branch,
+ file_name: "CHANGELOG",
+ commit_message: "Add changelog"
+ )
+ end
+ end
+
+ def add_license_path(project)
+ if project && !project.repository.license
+ namespace_project_new_blob_path(
+ project.namespace,
+ project,
+ project.default_branch,
+ file_name: "LICENSE",
+ commit_message: "Add license"
+ )
+ end
+ end
+
+ def contribution_guide_path(project)
if project && contribution_guide = project.repository.contribution_guide
namespace_project_blob_path(
project.namespace,
@@ -191,7 +231,7 @@ module ProjectsHelper
end
end
- def changelog_url(project)
+ def changelog_path(project)
if project && changelog = project.repository.changelog
namespace_project_blob_path(
project.namespace,
@@ -202,7 +242,7 @@ module ProjectsHelper
end
end
- def license_url(project)
+ def license_path(project)
if project && license = project.repository.license
namespace_project_blob_path(
project.namespace,
@@ -213,7 +253,7 @@ module ProjectsHelper
end
end
- def version_url(project)
+ def version_path(project)
if project && version = project.repository.version
namespace_project_blob_path(
project.namespace,
@@ -274,6 +314,21 @@ module ProjectsHelper
end
def readme_cache_key
- [@project.id, @project.commit.sha, "readme"].join('-')
+ sha = @project.commit.try(:sha) || 'nil'
+ [@project.id, sha, "readme"].join('-')
+ end
+
+ def round_commit_count(project)
+ count = project.commit_count
+
+ if count > 10000
+ '10000+'
+ elsif count > 5000
+ '5000+'
+ elsif count > 1000
+ '1000+'
+ else
+ count
+ end
end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index d3631d49ec6..f8e5afa9b01 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -31,10 +31,11 @@ class Ability
end
if project && project.public?
- [
+ rules = [
:read_project,
:read_wiki,
:read_issue,
+ :read_label,
:read_milestone,
:read_project_snippet,
:read_project_member,
@@ -42,6 +43,8 @@ class Ability
:read_note,
:download_code
]
+
+ rules - project_disabled_features_rules(project)
else
group = if subject.kind_of?(Group)
subject
@@ -102,28 +105,7 @@ class Ability
rules -= project_archived_rules
end
- unless project.issues_enabled
- rules -= named_abilities('issue')
- end
-
- unless project.merge_requests_enabled
- rules -= named_abilities('merge_request')
- end
-
- unless project.issues_enabled or project.merge_requests_enabled
- rules -= named_abilities('label')
- rules -= named_abilities('milestone')
- end
-
- unless project.snippets_enabled
- rules -= named_abilities('project_snippet')
- end
-
- unless project.wiki_enabled
- rules -= named_abilities('wiki')
- end
-
- rules
+ rules - project_disabled_features_rules(project)
end
end
@@ -158,12 +140,13 @@ class Ability
:create_project_snippet,
:update_issue,
:admin_issue,
- :admin_label,
+ :admin_label
]
end
def project_dev_rules
project_report_rules + [
+ :admin_merge_request,
:create_merge_request,
:create_wiki,
:push_code
@@ -205,6 +188,33 @@ class Ability
]
end
+ def project_disabled_features_rules(project)
+ rules = []
+
+ unless project.issues_enabled
+ rules += named_abilities('issue')
+ end
+
+ unless project.merge_requests_enabled
+ rules += named_abilities('merge_request')
+ end
+
+ unless project.issues_enabled or project.merge_requests_enabled
+ rules += named_abilities('label')
+ rules += named_abilities('milestone')
+ end
+
+ unless project.snippets_enabled
+ rules += named_abilities('project_snippet')
+ end
+
+ unless project.wiki_enabled
+ rules += named_abilities('wiki')
+ end
+
+ rules
+ end
+
def group_abilities(user, group)
rules = []
@@ -223,7 +233,8 @@ class Ability
if group.has_owner?(user) || user.admin?
rules.push(*[
:admin_group,
- :admin_namespace
+ :admin_namespace,
+ :admin_group_member
])
end
@@ -285,7 +296,7 @@ class Ability
rules = []
target_user = subject.user
group = subject.group
- can_manage = group_abilities(user, group).include?(:admin_group)
+ can_manage = group_abilities(user, group).include?(:admin_group_member)
if can_manage && (user != target_user)
rules << :update_group_member
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index fee52694099..6d1ad82a262 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -14,13 +14,14 @@
# default_branch_protection :integer default(2)
# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
+# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null
-# session_expire_delay :integer default(10080), not null
# default_project_visibility :integer
# default_snippet_visibility :integer
# restricted_signup_domains :text
-# user_oauth_applications :bool default(TRUE)
+# user_oauth_applications :boolean default(TRUE)
# after_sign_out_path :string(255)
+# session_expire_delay :integer default(10080), not null
#
class ApplicationSetting < ActiveRecord::Base
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 967ffd46db0..0ed0dd98a59 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -1,3 +1,17 @@
+# == Schema Information
+#
+# Table name: audit_events
+#
+# id :integer not null, primary key
+# author_id :integer not null
+# type :string(255) not null
+# entity_id :integer not null
+# entity_type :string(255) not null
+# details :text
+# created_at :datetime
+# updated_at :datetime
+#
+
class AuditEvent < ActiveRecord::Base
serialize :details, Hash
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 97846b06d72..c21e7fd0e3b 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -159,6 +159,16 @@ module Issuable
end
end
+ # Convert this Issuable class name to a format usable by Ability definitions
+ #
+ # Examples:
+ #
+ # issuable.class # => MergeRequest
+ # issuable.to_ability_name # => "merge_request"
+ def to_ability_name
+ self.class.to_s.underscore
+ end
+
private
def filter_superceded_votes(votes, notes)
diff --git a/app/models/group.rb b/app/models/group.rb
index 051c672cb33..cfb8faa1491 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -56,6 +56,12 @@ class Group < Namespace
name
end
+ def avatar_url(size = nil)
+ if avatar.present?
+ [gitlab_config.url, avatar.url].join
+ end
+ end
+
def owners
@owners ||= group_members.owners.map(&:user)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 8481a149e21..4628f478ca6 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -21,12 +21,13 @@
# import_url :string(255)
# visibility_level :integer default(0), not null
# archived :boolean default(FALSE), not null
+# avatar :string(255)
# import_status :string(255)
# repository_size :float default(0.0)
# star_count :integer default(0), not null
# import_type :string(255)
# import_source :string(255)
-# avatar :string(255)
+# commit_count :integer default(0)
#
require 'carrierwave/orm/activerecord'
@@ -36,7 +37,6 @@ class Project < ActiveRecord::Base
include Gitlab::ConfigHelper
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
- include Rails.application.routes.url_helpers
include Referable
include Sortable
@@ -316,7 +316,7 @@ class Project < ActiveRecord::Base
end
def web_url
- [gitlab_config.url, path_with_namespace].join('/')
+ Rails.application.routes.url_helpers.namespace_project_url(self.namespace, self)
end
def web_url_without_protocol
@@ -433,7 +433,7 @@ class Project < ActiveRecord::Base
if avatar.present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
- [gitlab_config.url, namespace_project_avatar_path(namespace, self)].join
+ Rails.application.routes.url_helpers.namespace_project_avatar_url(namespace, self)
end
end
@@ -563,7 +563,7 @@ class Project < ActiveRecord::Base
end
def http_url_to_repo
- [gitlab_config.url, '/', path_with_namespace, '.git'].join('')
+ "#{web_url}.git"
end
# Check if current branch name is marked as protected in the system
@@ -672,6 +672,10 @@ class Project < ActiveRecord::Base
update_attribute(:repository_size, repository.size)
end
+ def update_commit_count
+ update_attribute(:commit_count, repository.commit_count)
+ end
+
def forks_count
ForkedProjectLink.where(forked_from_project_id: self.id).count
end
@@ -689,14 +693,14 @@ class Project < ActiveRecord::Base
if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path)
true
else
- errors.add(:base, 'Failed to fork repository')
+ errors.add(:base, 'Failed to fork repository via gitlab-shell')
false
end
else
if gitlab_shell.add_repository(path_with_namespace)
true
else
- errors.add(:base, 'Failed to create repository')
+ errors.add(:base, 'Failed to create repository via gitlab-shell')
false
end
end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 0ea866fdb4a..ecdcd48ae60 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -22,8 +22,12 @@ class GitlabCiService < CiService
API_PREFIX = "api/v1"
prop_accessor :project_url, :token
- validates :project_url, presence: true, if: :activated?
- validates :token, presence: true, if: :activated?
+ validates :project_url,
+ presence: true,
+ format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
+ validates :token,
+ presence: true,
+ format: { with: /\A([A-Za-z0-9]+)\z/ }, if: :activated?
after_save :compose_service_hook, if: :activated?
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 41666bd6e38..46efbede2a2 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -94,18 +94,6 @@ class Repository
gitlab_shell.rm_tag(path_with_namespace, tag_name)
end
- def round_commit_count
- if commit_count > 10000
- '10000+'
- elsif commit_count > 5000
- '5000+'
- elsif commit_count > 1000
- '1000+'
- else
- commit_count
- end
- end
-
def branch_names
cache.fetch(:branch_names) { raw_repository.branch_names }
end
@@ -130,28 +118,29 @@ class Repository
cache.fetch(:size) { raw_repository.size }
end
+ def cache_keys
+ %i(size branch_names tag_names commit_count
+ readme version contribution_guide changelog license)
+ end
+
+ def build_cache
+ cache_keys.each do |key|
+ unless cache.exist?(key)
+ send(key)
+ end
+ end
+ end
+
def expire_cache
- %i(size branch_names tag_names commit_count graph_log
- readme version contribution_guide changelog license).each do |key|
+ cache_keys.each do |key|
cache.expire(key)
end
end
- def graph_log
- cache.fetch(:graph_log) do
- commits = raw_repository.log(limit: 6000, skip_merges: true,
- ref: root_ref)
-
- commits.map do |rugged_commit|
- commit = Gitlab::Git::Commit.new(rugged_commit)
-
- {
- author_name: commit.author_name,
- author_email: commit.author_email,
- additions: commit.stats.additions,
- deletions: commit.stats.deletions,
- }
- end
+ def rebuild_cache
+ cache_keys.each do |key|
+ cache.expire(key)
+ send(key)
end
end
@@ -463,8 +452,7 @@ class Repository
filename = nil
startline = 0
- lines = result.lines
- lines.each_with_index do |line, index|
+ result.each_line.each_with_index do |line, index|
if line =~ /^.*:.*:\d+:/
ref, filename, startline = line.split(':')
startline = startline.to_i - index
@@ -472,11 +460,11 @@ class Repository
end
end
- data = lines.map do |line|
- line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
- end
+ data = ""
- data = data.join("")
+ result.each_line do |line|
+ data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
+ end
OpenStruct.new(
filename: filename,
diff --git a/app/models/security_event.rb b/app/models/security_event.rb
index d131c11cb6c..68c00adad59 100644
--- a/app/models/security_event.rb
+++ b/app/models/security_event.rb
@@ -1,2 +1,16 @@
+# == Schema Information
+#
+# Table name: audit_events
+#
+# id :integer not null, primary key
+# author_id :integer not null
+# type :string(255) not null
+# entity_id :integer not null
+# entity_type :string(255) not null
+# details :text
+# created_at :datetime
+# updated_at :datetime
+#
+
class SecurityEvent < AuditEvent
end
diff --git a/app/models/user.rb b/app/models/user.rb
index fb330ff7185..2beefb1d614 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -57,6 +57,7 @@
# otp_backup_codes :text
# public_email :string(255) default(""), not null
# dashboard :integer default(0)
+# project_view :integer default(0)
#
require 'carrierwave/orm/activerecord'
@@ -274,6 +275,10 @@ class User < ActiveRecord::Base
value: login.to_s.downcase).first
end
+ def find_by_username!(username)
+ find_by!('lower(username) = ?', username.downcase)
+ end
+
def by_username_or_id(name_or_id)
where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 3a5a52e6f55..81535450ac1 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -19,7 +19,6 @@ class GitPushService
@project, @user = project, user
project.repository.expire_cache
- project.update_repository_size
if push_remove_branch?(ref, newrev)
@push_commits = []
@@ -59,6 +58,7 @@ class GitPushService
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks)
+ ProjectCacheWorker.perform_async(project.id)
end
protected
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 075a6118da2..1cc42b0b0ad 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -2,15 +2,15 @@ class GitTagPushService
attr_accessor :project, :user, :push_data
def execute(project, user, oldrev, newrev, ref)
- @project, @user = project, user
+ project.repository.expire_cache
+ @project, @user = project, user
@push_data = build_push_data(oldrev, newrev, ref)
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks)
-
- project.repository.expire_cache
+ ProjectCacheWorker.perform_async(project.id)
true
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index f1ef5ca84fe..15b3825f96a 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -27,8 +27,10 @@ class IssuableBaseService < BaseService
old_branch, new_branch)
end
- def filter_params
- unless can?(current_user, :admin_issue, project)
+ def filter_params(issuable_ability_name = :issue)
+ ability = :"admin_#{issuable_ability_name}"
+
+ unless can?(current_user, ability, project)
params.delete(:milestone_id)
params.delete(:label_ids)
params.delete(:assignee_id)
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index c3ca04a4343..770f32de944 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -10,6 +10,10 @@ module Issues
private
+ def filter_params
+ super(:issue)
+ end
+
def execute_hooks(issue, action = 'open')
issue_data = hook_data(issue, action)
issue.project.execute_hooks(issue_data, :issue_hooks)
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index e455fe95791..7b306a8a531 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -20,5 +20,11 @@ module MergeRequests
merge_request.project.execute_services(merge_data, :merge_request_hooks)
end
end
+
+ private
+
+ def filter_params
+ super(:merge_request)
+ end
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 011f6f6145e..b35aed005da 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -85,6 +85,8 @@ module Projects
@project.create_wiki if @project.wiki_enabled?
+ @project.build_missing_services
+
event_service.create_project(@project, current_user)
system_hook_service.execute_hooks_for(@project, :create)
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 187314872de..296497a4cd4 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -51,21 +51,22 @@
= paginate @projects, param_name: 'projects_page', theme: 'gitlab'
.col-md-6
- .panel.panel-default
- .panel-heading
- Add user(s) to the group:
- .panel-body.form-holder
- %p.light
- Read more about project permissions
- %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
+ - if can?(current_user, :admin_group_member, @group)
+ .panel.panel-default
+ .panel-heading
+ Add user(s) to the group:
+ .panel-body.form-holder
+ %p.light
+ Read more about project permissions
+ %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
- = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
- %div
- = users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all)
- %div.prepend-top-10
- = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
- %hr
- = button_tag 'Add users to group', class: "btn btn-create"
+ = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
+ %div
+ = users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all)
+ %div.prepend-top-10
+ = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
+ %hr
+ = button_tag 'Add users to group', class: "btn btn-create"
.panel.panel-default
.panel-heading
%h3.panel-title
@@ -86,7 +87,8 @@
(invited)
%span.pull-right.light
= member.human_access
- = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
- %i.fa.fa-minus.fa-inverse
+ - if can?(current_user, :destroy_group_member, member)
+ = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
+ %i.fa.fa-minus.fa-inverse
.panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml
index 0525552ebf8..3a788558226 100644
--- a/app/views/admin/identities/_form.html.haml
+++ b/app/views/admin/identities/_form.html.haml
@@ -8,7 +8,8 @@
.form-group
= f.label :provider, class: 'control-label'
.col-sm-10
- = f.select :provider, Gitlab::OAuth::Provider.names, { allow_blank: false }, class: 'form-control'
+ - values = Gitlab::OAuth::Provider.providers.map { |name| ["#{Gitlab::OAuth::Provider.label_for(name)} (#{name})", name] }
+ = f.select :provider, values, { allow_blank: false }, class: 'form-control'
.form-group
= f.label :extern_uid, "Identifier", class: 'control-label'
.col-sm-10
diff --git a/app/views/admin/identities/_identity.html.haml b/app/views/admin/identities/_identity.html.haml
index 671c4fbc677..7362d904b94 100644
--- a/app/views/admin/identities/_identity.html.haml
+++ b/app/views/admin/identities/_identity.html.haml
@@ -1,6 +1,6 @@
%tr
%td
- = identity.provider
+ = "#{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})"
%td
= identity.extern_uid
%td
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 54a39726771..9f5520603cd 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -1,5 +1,5 @@
= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
- = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus"
+ = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off"
= f.password_field :password, class: "form-control bottom", placeholder: "Password"
- if devise_mapping.rememberable?
.remember-me.checkbox
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 6ec741e4882..689cd6ed665 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -6,4 +6,4 @@
%label{for: "remember_me"}
= check_box_tag :remember_me, '1', false, id: 'remember_me'
%span Remember me
- = button_tag "#{server['label']} Sign in", class: "btn-save btn"
+ = button_tag "Sign in", class: "btn-save btn"
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index f8ba9d80ae8..ecf680e7b23 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -1,10 +1,8 @@
%p
%span.light
Sign in with &nbsp;
- - providers = additional_providers
+ - providers = button_based_providers
- providers.each do |provider|
%span.light
- - if default_providers.include?(provider)
- = link_to oauth_image_tag(provider), omniauth_authorize_path(resource_name, provider), method: :post, class: 'oauth-image-link'
- - else
- = link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), method: :post, class: "btn", "data-no-turbolink" => "true"
+ - has_icon = provider_has_icon?(provider)
+ = link_to provider_image_tag(provider), user_omniauth_authorize_path(provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true"
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
index c76574db457..bb5e479697d 100644
--- a/app/views/devise/shared/_signin_box.html.haml
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -6,7 +6,7 @@
.login-heading
%h3 Sign in
.login-body
- - if ldap_enabled?
+ - if form_based_providers.any?
%ul.nav.nav-tabs
- @ldap_servers.each_with_index do |server, i|
%li{class: (:active if i.zero?)}
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index b8409f64665..5ab5ffc238c 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -8,11 +8,10 @@
= image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
= render "events/event/created_project", event: event
- else
- = cache event do
- = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
- - if event.push?
- = render "events/event/push", event: event
- - elsif event.commented?
- = render "events/event/note", event: event
- - else
- = render "events/event/common", event: event
+ = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
+ - if event.push?
+ = render "events/event/push", event: event
+ - elsif event.commented?
+ = render "events/event/note", event: event
+ - else
+ = render "events/event/common", event: event
diff --git a/app/views/explore/projects/_project.html.haml b/app/views/explore/projects/_project.html.haml
index d65fb529373..d769c91545d 100644
--- a/app/views/explore/projects/_project.html.haml
+++ b/app/views/explore/projects/_project.html.haml
@@ -14,7 +14,7 @@
.repo-info
- unless project.empty_repo?
- = link_to pluralize(project.repository.round_commit_count, 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch)
+ = link_to pluralize(round_commit_count(project), 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch)
&middot;
= link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project)
&middot;
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index b460e0ff59e..acc7f8b28c2 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -24,7 +24,7 @@
= link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at)
- - if show_controls && can?(current_user, :admin_group, @group)
+ - if show_controls && can?(current_user, :admin_group_member, member)
= link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do
Resend invite
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index a70d1ff0697..dba395cc8fa 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -17,7 +17,7 @@
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
= button_tag 'Search', class: 'btn'
- - if current_user && current_user.can?(:admin_group, @group)
+ - if current_user && current_user.can?(:admin_group_member, @group)
.pull-right
= button_tag class: 'btn btn-new js-toggle-button', type: 'button' do
Add members
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index ee1b57278b6..c8662a15adb 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -33,7 +33,7 @@
= yield
%div.footer{style: "margin-top: 10px;"}
%p
- \—
+ &mdash;
%br
- if @target_url
#{link_to "View it on GitLab", @target_url}
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 378dfa2dce0..767fe2e0e9a 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -59,22 +59,22 @@
%div
= link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
- - if show_profile_social_tab?
+ - if button_based_providers.any?
.panel.panel-default
.panel-heading
Connected Accounts
.panel-body
.oauth-buttons.append-bottom-10
%p Click on icon to activate signin with one of the following services
- - enabled_social_providers.each do |provider|
+ - button_based_providers.each do |provider|
.btn-group
- = link_to oauth_image_tag(provider), omniauth_authorize_path(User, provider),
- method: :post, class: "btn btn-lg #{'active' if oauth_active?(provider)}"
- - if oauth_active?(provider)
+ = link_to provider_image_tag(provider), user_omniauth_authorize_path(provider), method: :post, class: "btn btn-lg #{'active' if auth_active?(provider)}", "data-no-turbolink" => "true"
+
+ - if auth_active?(provider)
= link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do
= icon('close')
- - if show_profile_username_tab?
+ - if current_user.can_change_username?
.panel.panel-warning.update-username
.panel-heading
Change Username
@@ -94,7 +94,7 @@
%div
= f.submit 'Save username', class: "btn btn-warning"
- - if show_profile_remove_tab?
+ - if signup_enabled?
.panel.panel-danger.remove-account
.panel-heading
Remove account
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index f905417f0e2..b76a5b636ac 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -7,13 +7,12 @@
%li= msg
.form-group
- = f.label :title, class: 'control-label'
- .col-sm-10= f.text_field :title, class: "form-control"
- .form-group
= f.label :key, class: 'control-label'
.col-sm-10
= f.text_area :key, class: "form-control", rows: 8
-
+ .form-group
+ = f.label :title, class: 'control-label'
+ .col-sm-10= f.text_field :title, class: "form-control"
.form-actions
= f.submit 'Add key', class: "btn btn-create"
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index 74268c9bde2..92dc58c10d7 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -5,7 +5,7 @@
Download the Google Authenticator application from App Store for iOS or Google
Play for Android and scan this code.
- More information is available in the #{link_to('documentation', help_page_path('workflow', 'two_factor_authentication'))}.
+ More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
%hr
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index 84742608986..f3b01ff3288 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -11,7 +11,7 @@
%td.old_line.diff-line-num{data: {linenumber: line_old}}
= link_to raw(line_old), "#"
%td.new_line= link_to raw(line_new) , "#"
- %td.line_content.noteable_line= line
+ %td.line_content.noteable_line= ' ' * @form.indent + line
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
%tr.line_holder{ id: @form.to }
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index ed4c601bcdb..977ca423f75 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -1,6 +1,7 @@
- too_big = diff_file.diff_lines.count > Commit::DIFF_SAFE_LINES
- if too_big
- %a.supp_diff_link Changes suppressed. Click to show
+ .suppressed-container
+ %a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show.
%table.text-file{class: "#{'hide' if too_big}"}
- last_line = 0
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index f99bc9a85eb..caed0e69dc8 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -3,7 +3,7 @@
Too many changes to show.
.pull-right
- unless diff_hard_limit_enabled?
- = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm btn-warning"
+ = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: :html)), class: "btn btn-sm btn-warning"
- if current_controller?(:commit) or current_controller?(:merge_requests)
- if current_controller?(:commit)
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 1b45bb1af0c..b6910c8f796 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -3,43 +3,42 @@
.issue-check
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
- = cache issue do
- .issue-title
- %span.issue-title-text
- = link_to_gfm issue.title, issue_path(issue), class: "row_title"
- .issue-labels
- - issue.labels.each do |label|
- = link_to_label(label, project: issue.project)
- .pull-right.light
- - if issue.closed?
- %span
- CLOSED
- - if issue.assignee
- = link_to_member(@project, issue.assignee, name: false)
- - note_count = issue.notes.user.count
- - if note_count > 0
- &nbsp;
- %span
- %i.fa.fa-comments
- = note_count
- - else
- &nbsp;
- %span.issue-no-comments
- %i.fa.fa-comments
- = 0
-
- .issue-info
- = "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
- - if issue.votes_count > 0
- = render 'votes/votes_inline', votable: issue
- - if issue.milestone
+ .issue-title
+ %span.issue-title-text
+ = link_to_gfm issue.title, issue_path(issue), class: "row_title"
+ .issue-labels
+ - issue.labels.each do |label|
+ = link_to_label(label, project: issue.project)
+ .pull-right.light
+ - if issue.closed?
+ %span
+ CLOSED
+ - if issue.assignee
+ = link_to_member(@project, issue.assignee, name: false)
+ - note_count = issue.notes.user.count
+ - if note_count > 0
&nbsp;
%span
- %i.fa.fa-clock-o
- = issue.milestone.title
- - if issue.tasks?
- %span.task-status
- = issue.task_status
+ %i.fa.fa-comments
+ = note_count
+ - else
+ &nbsp;
+ %span.issue-no-comments
+ %i.fa.fa-comments
+ = 0
+
+ .issue-info
+ = "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
+ - if issue.votes_count > 0
+ = render 'votes/votes_inline', votable: issue
+ - if issue.milestone
+ &nbsp;
+ %span
+ %i.fa.fa-clock-o
+ = issue.milestone.title
+ - if issue.tasks?
+ %span.task-status
+ = issue.task_status
- .pull-right.issue-updated-at
- %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
+ .pull-right.issue-updated-at
+ %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index b6d9b135c70..c57eee14143 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -31,6 +31,16 @@
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
+ - if @merge_request.open? and @merge_request.source_branch_exists?
+ .append-bottom-20
+ .slead
+ %span
+ Fetch the branch with
+ %strong.label-branch<
+ git fetch
+ \ #{@merge_request.source_project.http_url_to_repo}
+ \ #{@merge_request.source_branch}
+
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml"
@@ -56,11 +66,9 @@
#notes.notes.tab-pane.voting_notes
= render "projects/merge_requests/discussion"
#commits.commits.tab-pane
- - if current_page?(action: 'commits')
- = render "projects/merge_requests/show/commits"
+ - # This tab is always loaded via AJAX
#diffs.diffs.tab-pane
- - if current_page?(action: 'diffs')
- = render "projects/merge_requests/show/diffs"
+ - # This tab is always loaded via AJAX
.mr-loading-status
= spinner
diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml
index 8372afa61b5..9210798f39c 100644
--- a/app/views/projects/merge_requests/branch_from.js.haml
+++ b/app/views/projects/merge_requests/branch_from.js.haml
@@ -1,2 +1,3 @@
:plain
$(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}");
+ $('.js-timeago').timeago()
diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml
index f7ede0ded53..32fe2d535f3 100644
--- a/app/views/projects/merge_requests/branch_to.js.haml
+++ b/app/views/projects/merge_requests/branch_to.js.haml
@@ -1,2 +1,3 @@
:plain
$(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}");
+ $('.js-timeago').timeago()
diff --git a/app/views/projects/merge_requests/widget/open/_missing_branch.html.haml b/app/views/projects/merge_requests/widget/open/_missing_branch.html.haml
index 423fcd48e25..1c565bae80a 100644
--- a/app/views/projects/merge_requests/widget/open/_missing_branch.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_missing_branch.html.haml
@@ -6,9 +6,11 @@
%span.label.label-inverse= @merge_request.source_branch
does not exist in
%span.label.label-info= @merge_request.source_project_path
+ %br
+ %strong Please close this merge request and open a new merge request to change source branches.
- else
%span.label.label-inverse= @merge_request.target_branch
does not exist in
%span.label.label-info= @merge_request.target_project_path
- %br
- %strong Please close this merge request or change branches with existing one
+ %br
+ %strong Please close this merge request or change to another target branch.
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 14a0580f966..2ce5358fa74 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -5,6 +5,10 @@
%i.fa.fa-pencil-square-o
Edit
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close"
+ = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-sm btn-remove" do
+ %i.fa.fa-trash-o
+ Remove
+
%h4
= link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- if milestone.expired? and not milestone.closed?
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 5947498e379..7b1681df336 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -19,6 +19,9 @@
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
- else
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do
+ %i.fa.fa-trash-o
+ Remove
%hr
- if @milestone.issues.any? && @milestone.can_be_closed?
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index a88cf167511..52b5b8b877e 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -17,9 +17,9 @@
:javascript
network_graph = new Network({
- url: '#{namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))}',
- commit_url: '#{namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")}',
- ref: '#{@ref}',
+ url: "#{escape_javascript(@url)}",
+ commit_url: "#{escape_javascript(@commit_url)}",
+ ref: "#{escape_javascript(@ref)}",
commit_id: '#{@commit.id}'
})
new ShortcutsNetwork(network_graph.branch_graph)
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index 7472b33bb53..8f7d2e84c70 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -3,10 +3,7 @@
= note_target_fields(note)
= render layout: 'projects/md_preview', locals: { preview_class: 'note-text' } do
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
-
- .comment-hints.clearfix
- .pull-left #{link_to 'Markdown ', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }}
- .pull-right #{link_to 'Attach a file', '#', class: 'markdown-selector', tabindex: -1 }
+ = render 'projects/notes/hints'
.note-form-actions
.buttons
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 64f98741d45..3be8f44b282 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -8,18 +8,8 @@
= f.hidden_field :noteable_type
= render layout: 'projects/md_preview', locals: { preview_class: "note-text", referenced_users: true } do
- = render 'projects/zen', f: f, attr: :note,
- classes: 'note_text js-note-text'
-
- .comment-hints.clearfix
- .pull-left
- = link_to "Markdown ", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }
- tip:
- = random_markdown_tip
- .pull-right
- = link_to '#', class: 'markdown-selector', tabindex: -1 do
- Attach a file
- = icon('paperclip')
+ = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text'
+ = render 'projects/notes/hints'
.error-alert
.note-form-actions
diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml
new file mode 100644
index 00000000000..6e7929bdab0
--- /dev/null
+++ b/app/views/projects/notes/_hints.html.haml
@@ -0,0 +1,9 @@
+.comment-hints.clearfix
+ .pull-left
+ = link_to 'Markdown', help_page_path('markdown', 'markdown'), target: '_blank', tabindex: -1
+ tip:
+ = random_markdown_tip
+ .pull-right
+ = link_to '#', class: 'markdown-selector', tabindex: -1 do
+ = icon('paperclip')
+ Attach a file
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 5478a887f91..4a1009686c6 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -56,10 +56,9 @@
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
- = cache [note, 'markdown'] do
- .note-text
- = preserve do
- = markdown(note.note, {no_header_anchors: true})
+ .note-text
+ = preserve do
+ = markdown(note.note, {no_header_anchors: true})
= render 'projects/notes/edit_form', note: note
- if note.attachment.url
diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml
index 35c15cf3a9e..db7f244d002 100644
--- a/app/views/projects/refs/logs_tree.js.haml
+++ b/app/views/projects/refs/logs_tree.js.haml
@@ -11,9 +11,11 @@
- if @logs.present?
:plain
var current_url = location.href.replace(/\/?$/, '/');
- var log_url = '#{namespace_project_tree_url(@project.namespace, @project, tree_join(@ref, @path || '/'))}'.replace(/\/?$/, '/');
+ var log_url = "#{escape_javascript(@log_url)}".replace(/\/?$/, '/');
+
if(current_url == log_url) {
- // Load 10 more commit log for each file in tree
+ // Load more commit logs for each file in tree
// if we still on the same page
- ajaxGet('#{logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: (@offset + @limit))}');
+ var url = "#{escape_javascript(@more_log_url)}";
+ ajaxGet(url);
}
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 98d9053eb1d..4577b84ab89 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -6,33 +6,50 @@
= render 'shared/no_ssh'
= render 'shared/no_password'
-= render 'projects/last_push'
+- if prefer_readme?
+ = render 'projects/last_push'
+
= render "home_panel"
.project-stats
%ul.nav.nav-pills
%li
= link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
- = pluralize(number_with_delimiter(@repository.commit_count), 'commit')
+ = pluralize(number_with_delimiter(@project.commit_count), 'commit')
%li
= link_to namespace_project_branches_path(@project.namespace, @project) do
= pluralize(number_with_delimiter(@repository.branch_names.count), 'branch')
%li
= link_to namespace_project_tags_path(@project.namespace, @project) do
= pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
+
- if @repository.changelog
%li
- = link_to changelog_url(@project) do
+ = link_to changelog_path(@project) do
Changelog
- if @repository.license
%li
- = link_to license_url(@project) do
+ = link_to license_path(@project) do
License
- if @repository.contribution_guide
%li
- = link_to contribution_guide_url(@project) do
+ = link_to contribution_guide_path(@project) do
Contribution guide
+ - if current_user && can_push_branch?(@project, @project.default_branch)
+ - unless @repository.changelog
+ %li.missing
+ = link_to add_changelog_path(@project) do
+ Add Changelog
+ - unless @repository.license
+ %li.missing
+ = link_to add_license_path(@project) do
+ Add License
+ - unless @repository.contribution_guide
+ %li.missing
+ = link_to add_contribution_guide_path(@project) do
+ Add Contribution guide
+
- if @project.archived?
.text-warning.center.prepend-top-20
%p
diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml
index d304690d162..5048154cb2f 100644
--- a/app/views/projects/tree/_tree.html.haml
+++ b/app/views/projects/tree/_tree.html.haml
@@ -49,5 +49,5 @@
:javascript
// Load last commit log for each file in tree
$('#tree-slider').waitForImages(function() {
- ajaxGet('#{@logs_path}');
+ ajaxGet("#{escape_javascript(@logs_path)}");
});
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 83cd4c66672..5c4dd7f91ae 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -3,6 +3,10 @@
%h3.page-title
= @page.title
= render 'main_links'
+
+.wiki-last-edit-by
+ Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+
- if @page.historical?
.warning_message
This is an old version of this page.
@@ -16,6 +20,6 @@
= render_wiki_content(@page)
%hr
-
.wiki-last-edit-by
Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+
diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml
index 46990895d33..19e8c31975b 100644
--- a/app/views/shared/issuable/_context.html.haml
+++ b/app/views/shared/issuable/_context.html.haml
@@ -8,7 +8,7 @@
- else
none
.issuable-context-selectbox
- - if can?(current_user, :admin_issue, @project)
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true)
%div.prepend-top-20.clearfix
@@ -24,7 +24,7 @@
- else
none
.issuable-context-selectbox
- - if can?(current_user, :admin_issue, @project)
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'})
= hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index e434e1b6b98..ac8c1936c9e 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -38,7 +38,7 @@
.clearfix
.error-alert
%hr
-- if can?(current_user, :admin_issue, @project)
+- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.form-group
.issue-assignee
= f.label :assignee_id, class: 'control-label' do
@@ -100,7 +100,7 @@
= link_to 'Change branches', mr_change_branches_path(@merge_request)
.form-actions
- - if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted?
+ - if !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project)) && !issuable.persisted?
%p
Please review the
%strong #{link_to 'guidelines for contribution', guide_url}
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
new file mode 100644
index 00000000000..55cb6af232e
--- /dev/null
+++ b/app/workers/project_cache_worker.rb
@@ -0,0 +1,15 @@
+class ProjectCacheWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform(project_id)
+ project = Project.find(project_id)
+ project.update_repository_size
+ project.update_commit_count
+
+ if project.repository.root_ref
+ project.repository.build_cache
+ end
+ end
+end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 4e14e4883bb..b546f8777e1 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -27,7 +27,7 @@ class RepositoryImportWorker
project.import_finish
project.save
- project.update_repository_size
+ ProjectCacheWorker.perform_async(project.id)
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
end
end
diff --git a/config/application.rb b/config/application.rb
index 7e899cc3b5b..a96e22211e6 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -96,6 +96,7 @@ module Gitlab
end
redis_config_hash[:namespace] = 'cache:gitlab'
+ redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
config.cache_store = :redis_store, redis_config_hash
# This is needed for gitlab-shell
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index c32ac2042d0..56770335ddc 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -209,20 +209,29 @@ production: &base
# arguments, followed by optional 'args' which can be either a hash or an array.
# Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html
providers:
- # - { name: 'google_oauth2', app_id: 'YOUR_APP_ID',
+ # - { name: 'google_oauth2',
+ # label: 'Google',
+ # app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
# args: { access_type: 'offline', approval_prompt: '' } }
- # - { name: 'twitter', app_id: 'YOUR_APP_ID',
- # app_secret: 'YOUR_APP_SECRET'}
- # - { name: 'github', app_id: 'YOUR_APP_ID',
+ # - { name: 'twitter',
+ # app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET' }
+ # - { name: 'github',
+ # label: 'GitHub',
+ # app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
# args: { scope: 'user:email' } }
- # - { name: 'gitlab', app_id: 'YOUR_APP_ID',
+ # - { name: 'gitlab',
+ # label: 'GitLab.com',
+ # app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
# args: { scope: 'api' } }
- # - { name: 'bitbucket', app_id: 'YOUR_APP_ID',
- # app_secret: 'YOUR_APP_SECRET'}
- # - { name: 'saml',
+ # - { name: 'bitbucket',
+ # app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET' }
+ # - { name: 'saml',
+ # label: 'Our SAML Provider',
# args: {
# assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
# idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
@@ -247,6 +256,7 @@ production: &base
## Backup settings
backup:
path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)
+ # archive_permissions: 0640 # Permissions for the resulting backup.tar file (default: 0600)
# keep_time: 604800 # default: 0 (forever) (in seconds)
# upload:
# # Fog storage connection settings, see http://fog.io/storage/ .
@@ -338,6 +348,8 @@ test:
# user: YOUR_USERNAME
satellites:
path: tmp/tests/gitlab-satellites/
+ backup:
+ path: tmp/tests/backups
gitlab_shell:
path: tmp/tests/gitlab-shell/
repos_path: tmp/tests/repositories/
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 7b5d488f59e..bd76c918485 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -170,6 +170,7 @@ Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_s
Settings['backup'] ||= Settingslogic.new({})
Settings.backup['keep_time'] ||= 0
Settings.backup['path'] = File.expand_path(Settings.backup['path'] || "tmp/backups/", Rails.root)
+Settings.backup['archive_permissions'] ||= 0600
Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil })
# Convert upload connection settings to use symbol keys, to make Fog happy
if Settings.backup['upload']['connection']
diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb
index df73ec1304a..7f73546ac89 100644
--- a/config/initializers/7_omniauth.rb
+++ b/config/initializers/7_omniauth.rb
@@ -11,6 +11,7 @@ if Gitlab::LDAP::Config.enabled?
end
end
+OmniAuth.config.full_host = Settings.gitlab['url']
OmniAuth.config.allowed_request_methods = [:post]
#In case of auto sign-in, the GET method is used (users don't get to click on a button)
OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index d422acb31d6..6139ddbe6cd 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -6,7 +6,8 @@ Doorkeeper.configure do
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
# Put your resource owner authentication logic here.
- # Example implementation:
+ # Ensure user is redirected to redirect_uri after login
+ session[:user_return_to] = request.fullpath
current_user || redirect_to(new_user_session_url)
end
diff --git a/config/initializers/redis-store-fix-expiry.rb b/config/initializers/redis-store-fix-expiry.rb
deleted file mode 100644
index fce0a135330..00000000000
--- a/config/initializers/redis-store-fix-expiry.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# Monkey-patch Redis::Store to make 'setex' and 'expire' work with namespacing
-
-module Gitlab
- class Redis
- class Store
- module Namespace
- # Redis::Store#setex in redis-store 1.1.4 does not respect namespaces;
- # this new method does.
- def setex(key, expires_in, value, options=nil)
- namespace(key) { |key| super(key, expires_in, value) }
- end
-
- # Redis::Store#expire in redis-store 1.1.4 does not respect namespaces;
- # this new method does.
- def expire(key, expires_in)
- namespace(key) { |key| super(key, expires_in) }
- end
-
- private
-
- # Our new definitions of #setex and #expire above assume that the
- # #namespace method exists. Because we cannot be sure of that, we
- # re-implement the #namespace method from Redis::Store::Namespace so
- # that it is available for all Redis::Store instances, whether they use
- # namespacing or not.
- #
- # Based on lib/redis/store/namespace.rb L49-51 (redis-store 1.1.4)
- def namespace(key)
- if @namespace
- yield interpolate(key)
- else
- # This Redis::Store instance does not use a namespace so we should
- # just pass through the key.
- yield key
- end
- end
- end
- end
- end
-end
-
-Redis::Store.class_eval do
- include Gitlab::Redis::Store::Namespace
-end
diff --git a/config/routes.rb b/config/routes.rb
index 704cf705870..2436aae2b5a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -481,7 +481,7 @@ Gitlab::Application.routes.draw do
end
end
- resources :milestones, except: [:destroy], constraints: { id: /\d+/ } do
+ resources :milestones, constraints: { id: /\d+/ } do
member do
put :sort_issues
put :sort_merge_requests
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 87839770924..8f71198e47f 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -11,9 +11,42 @@ Sidekiq::Testing.inline! do
'https://github.com/twitter/flight.git',
'https://github.com/twitter/typeahead.js.git',
'https://github.com/h5bp/html5-boilerplate.git',
+ 'https://github.com/google/material-design-lite.git',
+ 'https://github.com/jlevy/the-art-of-command-line.git',
+ 'https://github.com/FreeCodeCamp/freecodecamp.git',
+ 'https://github.com/google/deepdream.git',
+ 'https://github.com/jtleek/datasharing.git',
+ 'https://github.com/WebAssembly/design.git',
+ 'https://github.com/airbnb/javascript.git',
+ 'https://github.com/tessalt/echo-chamber-js.git',
+ 'https://github.com/atom/atom.git',
+ 'https://github.com/ipselon/react-ui-builder.git',
+ 'https://github.com/mattermost/platform.git',
+ 'https://github.com/purifycss/purifycss.git',
+ 'https://github.com/facebook/nuclide.git',
+ 'https://github.com/wbkd/awesome-d3.git',
+ 'https://github.com/kilimchoi/engineering-blogs.git',
+ 'https://github.com/gilbarbara/logos.git',
+ 'https://github.com/gaearon/redux.git',
+ 'https://github.com/awslabs/s2n.git',
+ 'https://github.com/arkency/reactjs_koans.git',
+ 'https://github.com/twbs/bootstrap.git',
+ 'https://github.com/chjj/ttystudio.git',
+ 'https://github.com/DrBoolean/mostly-adequate-guide.git',
+ 'https://github.com/octocat/Spoon-Knife.git',
+ 'https://github.com/opencontainers/runc.git',
+ 'https://github.com/googlesamples/android-topeka.git'
]
- project_urls.each_with_index do |url, i|
+ # You can specify how many projects you need during seed execution
+ size = if ENV['SIZE'].present?
+ ENV['SIZE'].to_i
+ else
+ 8
+ end
+
+
+ project_urls.first(size).each_with_index do |url, i|
group_path, project_path = url.split('/')[-2..-1]
group = Group.find_by(path: group_path)
diff --git a/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb b/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb
index ffa22e6d5ef..61ff0af41f4 100644
--- a/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb
+++ b/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb
@@ -1,5 +1,7 @@
class AddSessionExpireDelayForApplicationSettings < ActiveRecord::Migration
def change
- add_column :application_settings, :session_expire_delay, :integer, default: 10080, null: false
+ unless column_exists?(:application_settings, :session_expire_delay)
+ add_column :application_settings, :session_expire_delay, :integer, default: 10080, null: false
+ end
end
-end \ No newline at end of file
+end
diff --git a/db/migrate/20150717130904_add_commits_count_to_project.rb b/db/migrate/20150717130904_add_commits_count_to_project.rb
new file mode 100644
index 00000000000..9b46daa5933
--- /dev/null
+++ b/db/migrate/20150717130904_add_commits_count_to_project.rb
@@ -0,0 +1,5 @@
+class AddCommitsCountToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :commit_count, :integer, default: 0
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d6c34a77ee6..a63c2d05821 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20150713160110) do
+ActiveRecord::Schema.define(version: 20150717130904) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -374,6 +374,7 @@ ActiveRecord::Schema.define(version: 20150713160110) do
t.integer "star_count", default: 0, null: false
t.string "import_type"
t.string "import_source"
+ t.integer "commit_count", default: 0
end
add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 7b0873a9111..bb551fc67f7 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -49,7 +49,8 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
- "description":"fixed login page css paddings"
+ "description":"fixed login page css paddings",
+ "work_in_progress": false
}
]
```
@@ -94,7 +95,8 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
- "description":"fixed login page css paddings"
+ "description":"fixed login page css paddings",
+ "work_in_progress": false
}
```
@@ -118,6 +120,7 @@ Parameters:
"project_id": 4,
"title": "Blanditiis beatae suscipit hic assumenda et molestias nisi asperiores repellat et.",
"description": "Qui voluptatibus placeat ipsa alias quasi. Deleniti rem ut sint. Optio velit qui distinctio.",
+ "work_in_progress": false,
"state": "reopened",
"created_at": "2015-02-02T19:49:39.159Z",
"updated_at": "2015-02-02T20:08:49.959Z",
@@ -336,14 +339,6 @@ Parameters:
```json
{
- "author": {
- "id": 1,
- "username": "admin",
- "email": "admin@example.com",
- "name": "Administrator",
- "blocked": false,
- "created_at": "2012-04-29T08:46:00Z"
- },
"note": "text1"
}
```
diff --git a/doc/api/users.md b/doc/api/users.md
index 5dca77b5c7b..7ba2db248ff 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -397,6 +397,138 @@ Parameters:
Will return `200 OK` on success, or `404 Not found` if either user or key cannot be found.
+## List emails
+
+Get a list of currently authenticated user's emails.
+
+```
+GET /user/emails
+```
+
+```json
+[
+ {
+ "id": 1,
+ "email": "email@example.com"
+ },
+ {
+ "id": 3,
+ "email": "email2@example.com"
+ }
+]
+```
+
+Parameters:
+
+- **none**
+
+## List emails for user
+
+Get a list of a specified user's emails. Available only for admin
+
+```
+GET /users/:uid/emails
+```
+
+Parameters:
+
+- `uid` (required) - id of specified user
+
+## Single email
+
+Get a single email.
+
+```
+GET /user/emails/:id
+```
+
+Parameters:
+
+- `id` (required) - email ID
+
+```json
+{
+ "id": 1,
+ "email": "email@example.com"
+}
+```
+
+## Add email
+
+Creates a new email owned by the currently authenticated user.
+
+```
+POST /user/emails
+```
+
+Parameters:
+
+- `email` (required) - email address
+
+```json
+{
+ "id": 4,
+ "email": "email@example.com"
+}
+```
+
+Will return created email with status `201 Created` on success. If an
+error occurs a `400 Bad Request` is returned with a message explaining the error:
+
+```json
+{
+ "message": {
+ "email": [
+ "has already been taken"
+ ]
+ }
+}
+```
+
+## Add email for user
+
+Create new email owned by specified user. Available only for admin
+
+```
+POST /users/:id/emails
+```
+
+Parameters:
+
+- `id` (required) - id of specified user
+- `email` (required) - email address
+
+Will return created email with status `201 Created` on success, or `404 Not found` on fail.
+
+## Delete email for current user
+
+Deletes email owned by currently authenticated user.
+This is an idempotent function and calling it on a email that is already deleted
+or not available results in `200 OK`.
+
+```
+DELETE /user/emails/:id
+```
+
+Parameters:
+
+- `id` (required) - email ID
+
+## Delete email for given user
+
+Deletes email owned by a specified user. Available only for admin.
+
+```
+DELETE /users/:uid/emails/:id
+```
+
+Parameters:
+
+- `uid` (required) - id of specified user
+- `id` (required) - email ID
+
+Will return `200 OK` on success, or `404 Not found` if either user or email cannot be found.
+
## Block user
Blocks the specified user. Available only for admin.
diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md
index 3e4400b544b..9491f8c91fe 100644
--- a/doc/gitlab-basics/README.md
+++ b/doc/gitlab-basics/README.md
@@ -2,7 +2,7 @@
Step-by-step guides on the basics of working with Git and GitLab.
-* [Start using Git on the commandline](start-using-git.md)
+* [Start using Git on the command line](start-using-git.md)
* [Create and add your SSH Keys](create-your-ssh-keys.md)
@@ -13,3 +13,11 @@ Step-by-step guides on the basics of working with Git and GitLab.
* [Create a project](create-project.md)
* [Create a group](create-group.md)
+
+* [Create a branch](create-branch.md)
+
+* [Fork a project](fork-project.md)
+
+* [Add a file](add-file.md)
+
+* [Create a Merge Request](add-merge-request.md)
diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md
new file mode 100644
index 00000000000..57136ac5c39
--- /dev/null
+++ b/doc/gitlab-basics/add-file.md
@@ -0,0 +1,31 @@
+# How to add a file
+
+You can create a file in your [shell](command-line-commands.md) or in GitLab.
+
+To create a file in GitLab, sign in to GitLab.
+
+Select a project on the right side of your screen:
+
+![Select a project](basicsimages/select_project.png)
+
+It's a good idea to [create a branch](create-branch.md), but it's not necessary.
+
+Go to the directory where you'd like to add the file and click on the "+" sign next to the name of the project and directory:
+
+![Create a file](basicsimages/create_file.png)
+
+Name your file (you can't add spaces, so you can use hyphens or underscores). Don't forget to include the markup language you'd like to use :
+
+![File name](basicsimages/file_name.png)
+
+Add all the information that you'd like to include in your file:
+
+![Add information](basicsimages/white_space.png)
+
+Add a commit message based on what you just added and then click on "commit changes":
+
+![Commit changes](basicsimages/commit_changes.png)
+
+### Note
+Besides its regular files, every directory needs a README.md or README.html file which works like an index, telling
+what the directory is about. It's the first document you'll find when you open a directory.
diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md
new file mode 100644
index 00000000000..236b4248ea2
--- /dev/null
+++ b/doc/gitlab-basics/add-merge-request.md
@@ -0,0 +1,42 @@
+# How to create a merge request
+
+Merge Requests are useful to integrate separate changes that you've made to a project, on different branches.
+
+To create a new Merge Request, sign in to GitLab.
+
+Go to the project where you'd like to merge your changes:
+
+![Select a project](basicsimages/select_project.png)
+
+Click on "Merge Requests" on the left side of your screen:
+
+![Merge requests](basicsimages/merge_requests.png)
+
+Click on "+ new Merge Request" on the right side of the screen:
+
+![New Merge Request](basicsimages/new_merge_request.png)
+
+Select a source branch or branch:
+
+![Select a branch](basicsimages/select_branch.png)
+
+Click on the "compare branches" button:
+
+![Compare branches](basicsimages/compare_branches.png)
+
+Add a title and a description to your Merge Request:
+
+![Add a title and description](basicsimages/title_description_mr.png)
+
+Select a user to review your Merge Request and to accept or close it. You may also select milestones and labels (they are optional). Then click on the "submit new Merge Request" button:
+
+![Add a new merge request](basicsimages/add_new_merge_request.png)
+
+Your Merge Request will be ready to be approved and published.
+
+### Note
+
+After you created a new branch, you'll immediately find a "create a Merge Request" button at the top of your screen.
+You may automatically create a Merge Request from your recently created branch when clicking on this button:
+
+![Automatic MR button](basicsimages/button-create-mr.png)
diff --git a/doc/gitlab-basics/basic-git-commands.md b/doc/gitlab-basics/basic-git-commands.md
index ed210ba5420..2b5767dd2d3 100644
--- a/doc/gitlab-basics/basic-git-commands.md
+++ b/doc/gitlab-basics/basic-git-commands.md
@@ -1,58 +1,58 @@
# Basic Git commands
-* Go to the master branch to pull the latest changes from there
+### Go to the master branch to pull the latest changes from there
```
git checkout master
```
-* Download the latest changes in the project, so that you work on an up-to-date copy (this is important to do every time you work on a project), while you setup tracking branches
+### Download the latest changes in the project
+This is for you to work on an up-to-date copy (it is important to do every time you work on a project), while you setup tracking branches.
```
git pull REMOTE NAME-OF-BRANCH -u
```
(REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch)
-* Create a branch (remember that spaces won't be recognized, you need to use a hyphen or underscore)
+### Create a branch
+Spaces won't be recognized, so you need to use a hyphen or underscore.
```
git checkout -b NAME-OF-BRANCH
```
-* Work on a branch that has already been created
+### Work on a branch that has already been created
```
git checkout NAME-OF-BRANCH
```
-* To see the changes you've made (it's important to be aware of what's happening and what's the status of your changes)
+### View the changes you've made
+It's important to be aware of what's happening and what's the status of your changes.
```
git status
```
-* Add changes to commit (you'll be able to see your changes in red when you type "git status")
+### Add changes to commit
+You'll see your changes in red when you type "git status".
```
git add CHANGES IN RED
git commit -m "DESCRIBE THE INTENTION OF THE COMMIT"
```
-* Send changes to gitlab.com
+### Send changes to gitlab.com
```
-git push origin NAME-OF-BRANCH
+git push REMOTE NAME-OF-BRANCH
```
-* Throw away all changes in the Git repository, but leave unstaged things
+### Delete all changes in the Git repository, but leave unstaged things
```
git checkout .
```
-* Delete all changes in the Git repository, including untracked files
+### Delete all changes in the Git repository, including untracked files
```
git clean -f
```
-* Remove all the changes that you don't want to send to gitlab.com
-```
-git add NAME-OF-FILE -all
-```
-
-* Merge created branch with master branch. You need to be in the created branch
+### Merge created branch with master branch
+You need to be in the created branch.
```
git checkout NAME-OF-BRANCH
git merge master
diff --git a/doc/gitlab-basics/basicsimages/button-create-mr.png b/doc/gitlab-basics/basicsimages/button-create-mr.png
new file mode 100644
index 00000000000..457af459bb9
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/button-create-mr.png
Binary files differ
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
index a596bf20c74..b03cca4029c 100644
--- a/doc/gitlab-basics/command-line-commands.md
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -2,46 +2,47 @@
## Start working on your project
-* In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to [GitLab.com](https://gitlab.com)
+In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to GitLab.
-* When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen
+When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen.
![Select a project](basicsimages/select_project.png)
-* To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step)
+To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step).
![Copy the HTTPS or SSH](basicsimages/https.png)
## On the command line
-* To clone your project, go to your computer's shell and type the following command
+### Clone your project
+Go to your computer's shell and type the following command:
```
git clone PASTE HTTPS OR SSH HERE
```
-* A clone of the project will be created in your computer
+A clone of the project will be created in your computer.
-* Go into a project, directory or file to work in it
+### Go into a project, directory or file to work in it
```
cd NAME-OF-PROJECT-OR-FILE
```
-* Go back one directory or file
+### Go back one directory or file
```
cd ../
```
-* To see what’s in the directory that you are in
+### View what’s in the directory that you are in
```
ls
```
-* Create a directory
+### Create a directory
```
mkdir NAME-OF-YOUR-DIRECTORY
```
-* Create a README.md or file in directory
+### Create a README.md or file in directory
```
touch README.md
nano README.md
@@ -51,22 +52,23 @@ nano README.md
#### Press: enter
```
-* Remove a file
+### Remove a file
```
rm NAME-OF-FILE
```
-* Remove a directory and all of its contents
+### Remove a directory and all of its contents
```
rm -rf NAME-OF-DIRECTORY
```
-* View history in the command line
+### View history in the command line
```
history
```
-* Carry out commands for which the account you are using lacks authority. (You will be asked for an administrator’s password)
+### Carry out commands for which the account you are using lacks authority
+You will be asked for an administrator’s password.
```
sudo
```
diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md
new file mode 100644
index 00000000000..7556b0f663e
--- /dev/null
+++ b/doc/gitlab-basics/create-branch.md
@@ -0,0 +1,39 @@
+# How to create a branch
+
+A branch is an independent line of development.
+
+New commits are recorded in the history for the current branch, which results in taking the source from someone’s repository (the place where the history of your work is stored) at certain point in time, and apply your own changes to it in the history of the project.
+
+To add changes to your GitLab project, you should create a branch. You can do it in your [shell](basic-git-commands.md) or in GitLab.
+
+To create a new branch in GitLab, sign in and then select a project on the right side of your screen:
+
+![Select a project](basicsimages/select_project.png)
+
+Click on "commits" on the menu on the left side of your screen:
+
+![Commits](basicsimages/commits.png)
+
+Click on the "branches" tab:
+
+![Branches](basicsimages/branches.png)
+
+Click on the "new branch" button on the right side of the screen:
+
+![New branch](basicsimages/newbranch.png)
+
+Fill out the information required:
+
+1. Add a name for your new branch (you can't add spaces, so you can use hyphens or underscores)
+
+1. On the "create from" space, add the the name of the branch you want to branch off from
+
+1. Click on the button "create branch"
+
+![Branch info](basicsimages/branch_info.png)
+
+### Note:
+
+You will be able to find and select the name of your branch in the white box next to a project's name:
+
+![Branch name](basicsimages/branch_name.png)
diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md
index 8e168395ff7..f80ae62e442 100644
--- a/doc/gitlab-basics/create-group.md
+++ b/doc/gitlab-basics/create-group.md
@@ -2,7 +2,7 @@
## Create a group
-Your projects in [GitLab.com](https://gitlab.com) can be organized in 2 different ways:
+Your projects in GitLab can be organized in 2 different ways:
under your own namespace for single projects, such as ´your-name/project-1'; or under groups.
If you organize your projects under a group, it works like a folder. You can manage your group members' permissions and access to the projects.
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index e3963f66010..b545d62549d 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -1,14 +1,12 @@
# How to create a project in GitLab
-## Create a project
+To create a new project, sign in to GitLab.
-* Sign in to [GitLab.com](https://gitlab.com)
-
-* Go to your Dashboard and click on "new project" on the right side of your screen
+Go to your Dashboard and click on "new project" on the right side of your screen.
![Create a project](basicsimages/new_project.png)
-* Fill out the required information
+Fill out the required information:
1. Project path or the name of your project (you can't add spaces, so you can use hyphens or underscores)
diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md
index e1acc2bd65b..c8a73feb028 100644
--- a/doc/gitlab-basics/create-your-ssh-keys.md
+++ b/doc/gitlab-basics/create-your-ssh-keys.md
@@ -4,34 +4,34 @@ You need to connect your computer to your GitLab account through SSH Keys. They
## Generate your SSH Key
-* Create an account on GitLab. Sign up and check your email for your confirmation link
+Create an account on GitLab. Sign up and check your email for your confirmation link.
-* After you confirm, go to [GitLab.com](https://about.gitlab.com/) and sign in to your account
+After you confirm, go to GitLab and sign in to your account.
## Add your SSH Key
-* At the top right corner, click on "profile settings"
+At the top right corner, click on "profile settings":
![profile settings](basicsimages/profile_settings.png)
-* On the left side menu click on "SSH Keys"
+On the left side menu click on "SSH Keys":
![SSH Keys](basicsimages/shh_keys.png)
-* Then click on the green button "Add SSH Key"
+Then click on the green button "Add SSH Key":
![Add SSH Key](basicsimages/add_sshkey.png)
-* There, you should paste the SSH Key that your commandline will generate for you. Below you'll find the steps to generate it
+There, you should paste the SSH Key that your command line will generate for you. Below you'll find the steps to generate it:
![Paste SSH Key](basicsimages/paste_sshkey.png)
-## To generate an SSH Key on your commandline
+## To generate an SSH Key on your command line
-* Go to your [commandline](start-using-git.md) and follow the [instructions](https://gitlab.com/help/ssh/README) to generate it
+Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate it.
-* Copy the SSH Key that your commandline created and paste it on the "Key" box on the GitLab page. The title will be added automatically
+Copy the SSH Key that your command line created and paste it on the "Key" box on the GitLab page. The title will be added automatically.
![Paste SSH Key](basicsimages/key.png)
-* Now, you'll be able to use Git over SSH, instead of Git over HTTP.
+Now, you'll be able to use Git over SSH, instead of Git over HTTP.
diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md
new file mode 100644
index 00000000000..5f8b81ea919
--- /dev/null
+++ b/doc/gitlab-basics/fork-project.md
@@ -0,0 +1,19 @@
+# How to fork a project
+
+A fork is a copy of an original repository that you can put somewhere else
+or where you can experiment and apply changes that you can later decide if
+publishing or not, without affecting your original project.
+
+It takes just a few steps to fork a project in GitLab.
+
+Sign in to GitLab.
+
+Select a project on the right side of your screen:
+
+![Select a project](basicsimages/select_project.png)
+
+Click on the "fork" button on the right side of your screen:
+
+![Fork](basicsimages/fork.png)
+
+Click on the user or group to where you'd like to add the forked project.
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index 21d93ed2e4d..b2ceda025c0 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -1,10 +1,10 @@
-# Start using Git on the commandline
+# Start using Git on the command line
-If you want to start using a Git and GitLab, make sure that you have created an account on [GitLab.com](https://about.gitlab.com/)
+If you want to start using a Git and GitLab, make sure that you have created an account on GitLab.
## Open a shell
-* Depending on your operating system, find the shell of your preference. Here are some suggestions
+Depending on your operating system, find the shell of your preference. Here are some suggestions.
- [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on Mac OSX
@@ -14,54 +14,48 @@ If you want to start using a Git and GitLab, make sure that you have created an
## Check if Git has already been installed
-* Git is usually preinstalled on Mac and Linux
-
-* Type the following command and then press enter
+Git is usually preinstalled on Mac and Linux.
+Type the following command and then press enter:
```
git --version
```
-* You should receive a message that will tell you which Git version you have in your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
+You should receive a message that will tell you which Git version you have in your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
-* If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window
+If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window.
-* After you finished installing, open a new shell and type "git --version" again to verify that it was correctly installed
+After you finished installing, open a new shell and type "git --version" again to verify that it was correctly installed.
## Add your Git username and set your email
-* It is important because every Git commit that you create will use this information
-
-* On your shell, type the following command to add your username
+It is important because every Git commit that you create will use this information.
+On your shell, type the following command to add your username:
```
git config --global user.name ADD YOUR USERNAME
```
-* Then verify that you have the correct username
-
+Then verify that you have the correct username:
```
git config --global user.name
```
-* To set your email address, type the following command
-
+To set your email address, type the following command:
```
git config --global user.email ADD YOUR EMAIL
```
-* To verify that you entered your email correctly, type
-
+To verify that you entered your email correctly, type:
```
git config --global user.email
```
-* You'll need to do this only once because you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project
+You'll need to do this only once because you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project.
## Check your information
-* To view the information that you entered, type
-
+To view the information that you entered, type:
```
git config --global --list
```
diff --git a/doc/integration/gitlab_actions.png b/doc/integration/gitlab_actions.png
deleted file mode 100644
index b08f54d137b..00000000000
--- a/doc/integration/gitlab_actions.png
+++ /dev/null
Binary files differ
diff --git a/doc/integration/gitlab_buttons_in_gmail.md b/doc/integration/gitlab_buttons_in_gmail.md
deleted file mode 100644
index e35bb8ba693..00000000000
--- a/doc/integration/gitlab_buttons_in_gmail.md
+++ /dev/null
@@ -1,28 +0,0 @@
-# GitLab buttons in Gmail
-
-GitLab supports [Google actions in email](https://developers.google.com/gmail/markup/actions/actions-overview).
-
-If correctly setup, emails that require an action will be marked in Gmail.
-
-![gitlab_actions](gitlab_actions.png)
-
-To get this functioning, you need to be registered with Google.
-[See how to register with Google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
-
-To aid the registering with Google, GitLab offers a rake task that will send an email to Google whitelisting email address from your GitLab server.
-
-To check what would be sent to the Google email address, run the rake task:
-
-```bash
-bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production
-```
-
-**This will not send the email but give you the output of how the mail will look.**
-
-Copy the output of the rake task to [Google email markup tester](https://www.google.com/webmasters/markup-tester/u/0/) and press "Validate".
-
-If you receive "No errors detected" message from the tester you can send the email using:
-
-```bash
-bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production SEND=true
-```
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 8e2a602ec35..2010cb9b8a1 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -84,7 +84,7 @@ Existing users can enable OmniAuth for specific providers after the account is c
1. Sign in normally - whether standard sign in, LDAP, or another OmniAuth provider.
1. Go to profile settings (the silhouette icon in the top right corner).
1. Select the "Account" tab.
-1. Under "Social Accounts" select the desired OmniAuth provider, such as Twitter.
+1. Under "Connected Accounts" select the desired OmniAuth provider, such as Twitter.
1. The user will be redirected to the provider. Once the user authorized GitLab they will be redirected back to GitLab.
The chosen OmniAuth provider is now active and can be used to sign in to GitLab from then on.
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index fe9091ad9a8..1350c8f693c 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -2,9 +2,7 @@
To enable the Twitter OmniAuth provider you must register your application with Twitter. Twitter will generate a client ID and secret key for you to use.
-1. Sign in to [Twitter Developers](https://dev.twitter.com/) area.
-
-1. Hover over the avatar in the top right corner and select "My applications."
+1. Sign in to [Twitter Application Management](https://apps.twitter.com/).
1. Select "Create new app"
@@ -14,18 +12,18 @@ To enable the Twitter OmniAuth provider you must register your application with
- Description: Create a description.
- Website: The URL to your GitLab installation. 'https://gitlab.example.com'
- Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback'
- - Agree to the "Rules of the Road."
+ - Agree to the "Developer Agreement".
![Twitter App Details](twitter_app_details.png)
1. Select "Create your Twitter application."
1. Select the "Settings" tab.
-1. Underneath the Callback URL check the box next to "Allow this application to be used to Sign in the Twitter."
+1. Underneath the Callback URL check the box next to "Allow this application to be used to Sign in with Twitter."
1. Select "Update settings" at the bottom to save changes.
-1. Select the "API Keys" tab.
+1. Select the "Keys and Access Tokens" tab.
1. You should now see an API key and API secret (see screenshot). Keep this page open as you continue configuration.
@@ -78,4 +76,4 @@ To enable the Twitter OmniAuth provider you must register your application with
1. Restart GitLab for the changes to take effect.
-On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
+On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in. \ No newline at end of file
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 70b7e17795d..7a6a1958445 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -6,6 +6,9 @@ If a user is both in a project group and in the project itself, the highest perm
If a user is a GitLab administrator they receive all permissions.
+To add or import a user, you can follow the [project users and members
+documentation](doc/workflow/add-user/add-user.md).
+
## Project
| Action | Guest | Reporter | Developer | Master | Owner |
@@ -17,6 +20,7 @@ If a user is a GitLab administrator they receive all permissions.
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
+| Manage merge requests | | | ✓ | ✓ | ✓ |
| Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ |
diff --git a/doc/profile/two_factor_authentication.md b/doc/profile/two_factor_authentication.md
index fb215c8b269..f60ce35d3e2 100644
--- a/doc/profile/two_factor_authentication.md
+++ b/doc/profile/two_factor_authentication.md
@@ -63,5 +63,10 @@ your phone's application or a recovery code to log in.
1. Go to **Account**.
1. Click **Disable Two-factor Authentication**.
+## Note to GitLab administrators
+
+You need to take special care to that 2FA keeps working after
+[restoring a GitLab backup](../raketasks/backup_restore.md).
+
[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
[FreeOTP]: https://fedorahosted.org/freeotp/
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index 770b7a70fe0..a8dc5c24df2 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -7,3 +7,4 @@
- [User management](user_management.md)
- [Web hooks](web_hooks.md)
- [Import](import.md) of git repositories in bulk
+- [Rebuild authorized_keys file](http://doc.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators \ No newline at end of file
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 39a13b14fba..05324b33022 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -9,6 +9,13 @@ This archive will be saved in backup_path (see `config/gitlab.yml`).
The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup.
You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
+You need to keep a separate copy of `/etc/gitlab/gitlab-secrets.json`
+(for omnibus packages) or `/home/git/gitlab/.secret` (for installations
+from source). This file contains the database encryption key used
+for two-factor authentication. If you restore a GitLab backup without
+restoring the database encryption key, users who have two-factor
+authentication enabled will loose access to your GitLab server.
+
If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)*
```
@@ -141,17 +148,58 @@ with the name of your bucket:
}
```
+## Backup archive permissions
+
+The backup archives created by GitLab (123456_gitlab_backup.tar) will have owner/group git:git and 0600 permissions by default.
+This is meant to avoid other system users reading GitLab's data.
+If you need the backup archives to have different permissions you can use the 'archive_permissions' setting.
+
+```
+# In /etc/gitlab/gitlab.rb, for omnibus packages
+gitlab_rails['backup_archive_permissions'] = 0644 # Makes the backup archives world-readable
+```
+
+```
+# In gitlab.yml, for installations from source:
+ backup:
+ archive_permissions: 0644 # Makes the backup archives world-readable
+```
+
## Storing configuration files
-Please be informed that a backup does not store your configuration files.
+Please be informed that a backup does not store your configuration
+files. One reason for this is that your database contains encrypted
+information for two-factor authentication. Storing encrypted
+information along with its key in the same place defeats the purpose
+of using encryption in the first place!
+
If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration).
If you have a cookbook installation there should be a copy of your configuration in Chef.
-If you have an installation from source, please consider backing up your `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
+If you have an installation from source, please consider backing up your `.secret` file, `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
+
+At the very **minimum** you should backup `/etc/gitlab/gitlab-secrets.json`
+(Omnibus) or `/home/git/gitlab/.secret` (source) to preserve your
+database encryption key.
## Restore a previously created backup
You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
+### Prerequisites
+
+You need to have a working GitLab installation before you can perform
+a restore. This is mainly because the system user performing the
+restore actions ('git') is usually not allowed to create or delete
+the SQL database it needs to import data into ('gitlabhq_production').
+All existing data will be either erased (SQL) or moved to a separate
+directory (repositories, uploads).
+
+If some or all of your GitLab users are using two-factor authentication
+(2FA) then you must also make sure to restore
+`/etc/gitlab/gitlab-secrets.json` (Omnibus) or `/home/git/gitlab/.secret`
+(installations from source). Note that you need to run `gitlab-ctl
+reconfigure` after changing `gitlab-secrets.json`.
+
### Installation from source
```
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index 3bc92187218..552b24195b1 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -6,6 +6,7 @@ It starts 7 working days before the release.
The release manager doesn't have to perform all the work but must ensure someone is assigned.
The current release manager must schedule the appointment of the next release manager.
The new release manager should create overall issue to track the progress.
+The release manager should be the only person pushing/merging commits to the x-y-stable branches.
## Release Manager
@@ -67,7 +68,7 @@ Xth: (2 working days before the 22nd)
Xth: (1 working day before the 22nd)
- [ ] Merge CE stable into EE stable
-- [ ] Create (hopefully final) CE, EE, CI release candidates (#LINK)
+- [ ] Create CE, EE, CI release candidates (#LINK) (hopefully final ones with the same commit as the release tomorrow)
- [ ] Create Omnibus tags and build packages for the latest release candidates
- [ ] Update GitLab.com with the latest RC (#LINK)
- [ ] Update ci.gitLab.com with the latest RC (#LINK)
@@ -80,10 +81,10 @@ workday to quickly fix any issues.
- [ ] Merge CE stable into EE stable (#LINK)
- [ ] Create the 'x.y.0' tag with the [release tools](https://dev.gitlab.org/gitlab/release-tools) (#LINK)
-- [ ] BEFORE 11AM CET Create and push omnibus tags for x.y.0 (will auto-release the packages) (#LINK)
-- [ ] BEFORE 12AM CET Publish the release blog post (#LINK)
+- [ ] Try to do before 11AM CET: Create and push omnibus tags for x.y.0 (will auto-release the packages) (#LINK)
+- [ ] Try to do before 12AM CET: Publish the release blog post (#LINK)
- [ ] Tweet about the release (blog post) (#LINK)
-- [ ] Schedule a second tweet of the release announcement at 6PM CET / 9AM PST
+- [ ] Schedule a second tweet of the release announcement with the same text at 6PM CET / 9AM PST
```
@@ -219,4 +220,4 @@ Consider creating a post on Hacker News.
## Create a WIP blogpost for the next release
-Create a WIP blogpost using [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md).
+Create a WIP blogpost using [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md). \ No newline at end of file
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 5f44f9351dd..7cdcd11c04c 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -105,3 +105,6 @@ IdentityFile ~/my-ssh-key-directory/company-com-private-key-filename
Note in the gitlab.com example above a username was specified to override the default chosen by OpenSSH (your local username). This is only required if your local and remote usernames differ.
Due to the wide variety of SSH clients and their very large number of configuration options, further explanation of these topics is beyond the scope of this document.
+
+Public SSH keys need to be unique, as they will bind to your account. Your SSH key is the only identifier you'll
+have when pushing code via SSH. That's why it needs to uniquely map to a single user.
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 1f39d02bdf3..3915198ad2a 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -10,6 +10,7 @@
- [Notification emails](notifications.md)
- [Project Features](project_features.md)
- [Project forking workflow](forking_workflow.md)
+- [Project users](add-user/add-user.md)
- [Protected branches](protected_branches.md)
- [Web Editor](web_editor.md)
- ["Work In Progress" Merge Requests](wip_merge_requests.md)
diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md
new file mode 100644
index 00000000000..8c9b4f72631
--- /dev/null
+++ b/doc/workflow/add-user/add-user.md
@@ -0,0 +1,25 @@
+# Project users
+
+You can manage the groups and users and their access levels in all of your projects. You can also personalize the access level you give each user, per project.
+
+Here's how to add or import users to your projects.
+
+You should have 'master' or 'owner' permissions to add or import a new user
+to your project.
+
+To add or import a user, go to your project and click on "Members" on the left side of your screen:
+
+![Members](images/members.png)
+
+Select "Add members" or "Import members" on the right side of your screen:
+
+![Add or Import](images/add-members.png)
+
+If you are adding a user, select the user and the [permission level](doc/permissions/permissions.md) that you'd like to
+give the user:
+
+![Add or Import](images/new-member.png)
+
+If you are importing a user, follow the steps to select the project where you'd like to import the user from:
+
+![Add or Import](images/select-project.png)
diff --git a/doc/workflow/add-user/images/add-members.png b/doc/workflow/add-user/images/add-members.png
new file mode 100644
index 00000000000..2805c5764a5
--- /dev/null
+++ b/doc/workflow/add-user/images/add-members.png
Binary files differ
diff --git a/doc/workflow/add-user/images/members.png b/doc/workflow/add-user/images/members.png
new file mode 100644
index 00000000000..f1797b95f67
--- /dev/null
+++ b/doc/workflow/add-user/images/members.png
Binary files differ
diff --git a/doc/workflow/add-user/images/new-member.png b/doc/workflow/add-user/images/new-member.png
new file mode 100644
index 00000000000..d500daea56e
--- /dev/null
+++ b/doc/workflow/add-user/images/new-member.png
Binary files differ
diff --git a/doc/workflow/add-user/images/select-project.png b/doc/workflow/add-user/images/select-project.png
new file mode 100644
index 00000000000..dd3844edff8
--- /dev/null
+++ b/doc/workflow/add-user/images/select-project.png
Binary files differ
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 0e87dc74217..f608674faf6 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -31,7 +31,7 @@ We think there is still room for improvement and will detail a set of practices
## Git flow and its problems
-[![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png)
+![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png)
Git flow was one of the first proposals to use git branches and it has gotten a lot of attention.
It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes.
@@ -54,7 +54,7 @@ And doing releases doesn't automatically mean also doing hotfixes.
![Master branch with feature branches merged in](github_flow.png)
- In reaction to git flow a simpler alternative was detailed, [GitHub flow](https://guides.github.com/introduction/flow/index.html).
+In reaction to git flow a simpler alternative was detailed, [GitHub flow](https://guides.github.com/introduction/flow/index.html).
This flow has only feature branches and a master branch.
This is very simple and clean, many organizations have adopted it with great success.
Atlassian recommends [a similar strategy](http://blogs.atlassian.com/2014/01/simple-git-workflow-simple/) although they rebase feature branches.
@@ -131,7 +131,7 @@ When you feel comfortable with it to be merged you assign it to the person that
There is room for more feedback and after the assigned person feels comfortable with the result the branch is merged.
If the assigned person does not feel comfortable they can close the merge request without merging.
-In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/permissions/permissions.md).
+In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](http://doc.gitlab.com/ce/permissions/permissions.html).
So if you want to merge it into a protected branch you assign it to someone with master authorizations.
## Issues with GitLab flow
@@ -216,7 +216,7 @@ This prevents creating a merge commit when merging master into your feature bran
However, just like with squashing you should never rebase commits you have pushed to a remote server.
This makes it impossible to rebase work in progress that you already shared with your team which is something we recommend.
When using rebase to keep your feature branch updated you [need to resolve similar conflicts again and again](http://blogs.atlassian.com/2013/10/git-team-workflows-merge-or-rebase/).
-You can reuse recorded resolutions (rerere) sometimes, but with without rebasing you only have to solve the conflicts one time and you’re set.
+You can reuse recorded resolutions (rerere) sometimes, but without rebasing you only have to solve the conflicts one time and you’re set.
There has to be a better way to avoid many merge commits.
The way to prevent creating many merge commits is to not frequently merge master into the feature branch.
diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md
index 19395657719..cd98d1b9852 100644
--- a/doc/workflow/importing/README.md
+++ b/doc/workflow/importing/README.md
@@ -6,4 +6,7 @@
4. [SVN](migrating_from_svn.md)
### Note
-* If you'd like to migrate from a self-hosted GitLab instance to GitLab.com, you can copy your repos by changing the remote and pushing to the new server; but issues and merge requests can't be imported. \ No newline at end of file
+* If you'd like to migrate from a self-hosted GitLab instance to GitLab.com, you can copy your repos by changing the remote and pushing to the new server; but issues and merge requests can't be imported.
+
+* Repositories are imported to GitLab via HTTP.
+If the repository is too large, it can timeout. We have a soft limit of 10GB.
diff --git a/doc/workflow/labels.md b/doc/workflow/labels.md
index 085b7baf5ce..6e4840ca5ae 100644
--- a/doc/workflow/labels.md
+++ b/doc/workflow/labels.md
@@ -1,6 +1,6 @@
# Labels
-In GitLab, you can easily tag issues and merge requests. If you have permission level `Developer` or higher, you can manage labels. To create, edit or delete a label, go to a project and then to `Issues` and then `Labels`.
+In GitLab, you can easily tag issues and Merge Requests. If you have permission level `Developer` or higher, you can manage labels. To create, edit or delete a label, go to a project and then to `Issues` and then `Labels`.
Here you can create a new label.
@@ -14,3 +14,5 @@ If you want to change an existing label, press edit next to the listed label.
You will be presented with the same form as when creating a new label.
![edit label](labels/label3.png)
+
+You can add labels to Merge Requests when you create or edit them.
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 86f6c896a6d..05521af6963 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -30,7 +30,9 @@ RUN ( \
echo "" && \
echo "# Docker options" && \
echo "# Prevent Postgres from trying to allocate 25% of total memory" && \
- echo "postgresql['shared_buffers'] = '1MB'" ) >> /etc/gitlab/gitlab.rb
+ echo "postgresql['shared_buffers'] = '1MB'" ) >> /etc/gitlab/gitlab.rb && \
+ mkdir -p /assets/ && \
+ cp /etc/gitlab/gitlab.rb /assets/gitlab.rb
# Expose web & ssh
EXPOSE 443 80 22
diff --git a/docker/README.md b/docker/README.md
index 23093d35759..e4d56cdb336 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -165,3 +165,5 @@ sudo docker push gitlab/gitlab-ce:latest
Please see the [troubleshooting](troubleshooting.md) file in this directory.
Note: We use `fig.yml` to have compatibility with fig and because docker-compose also supports it.
+
+Our docker image runs chef at every start to generate GitLab configuration.
diff --git a/docker/assets/wrapper b/docker/assets/wrapper
index 966b2cab4a1..8bc8370fbc9 100755
--- a/docker/assets/wrapper
+++ b/docker/assets/wrapper
@@ -13,4 +13,9 @@ function entrypoint() {
gitlab-ctl tail # tail all logs
}
+if [[ ! -e /etc/gitlab/gitlab.rb ]]; then
+ cp /assets/gitlab.rb /etc/gitlab/gitlab.rb
+ chmod 0600 /etc/gitlab/gitlab.rb
+fi
+
entrypoint
diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature
index 9ac65b1257c..bfbaaec5a35 100644
--- a/features/project/issues/milestones.feature
+++ b/features/project/issues/milestones.feature
@@ -17,6 +17,10 @@ Feature: Project Issues Milestones
And I submit new milestone "v2.3"
Then I should see milestone "v2.3"
+ Scenario: I delete new milestone
+ Given I click link to remove milestone "v2.2"
+ And I should see no milestones
+
@javascript
Scenario: Listing closed issues
Given the milestone has open and closed issues
diff --git a/features/project/network_graph.feature b/features/project/network_graph.feature
index 8beb6043aff..6cc89a15a78 100644
--- a/features/project/network_graph.feature
+++ b/features/project/network_graph.feature
@@ -11,6 +11,11 @@ Feature: Project Network Graph
And page should have "master" on graph
@javascript
+ Scenario: I should see project network with 'test' branch
+ When I visit project network page on branch 'test'
+ Then page should have 'test' on graph
+
+ @javascript
Scenario: I should switch "branch" and "tag"
When I switch ref to "feature"
Then page should select "feature" in select box
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index af68cb96ed9..d3a77466a35 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -158,3 +158,10 @@ Feature: Project Source Browse Files
Given I visit project source page for "6d394385cf567f80a8fd85055db1ab4c5295806f"
And I click on ".gitignore" file in repo
Then I don't see the permalink link
+
+ @javascript
+ Scenario: I browse code with single quotes in the ref
+ Given I switch ref to 'test'
+ And I see the ref 'test' has been selected
+ And I visit the 'test' tree
+ Then I see the commit data
diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb
index 6c4b91586d6..49e64eeff71 100644
--- a/features/steps/admin/users.rb
+++ b/features/steps/admin/users.rb
@@ -3,6 +3,14 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
include SharedPaths
include SharedAdmin
+ before do
+ allow(Devise).to receive(:omniauth_providers).and_return([:twitter, :twitter_updated])
+ end
+
+ after do
+ allow(Devise).to receive(:omniauth_providers).and_call_original
+ end
+
step 'I should see all users' do
User.all.each do |user|
expect(page).to have_content user.name
@@ -121,7 +129,6 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
end
step 'I visit "Pete" identities page in admin' do
- allow(Gitlab::OAuth::Provider).to receive(:names).and_return(%w(twitter twitter_updated))
visit admin_user_identities_path(@user)
end
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index 708c5243947..61e62c2adbd 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -56,4 +56,12 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
step 'I should see 3 issues' do
expect(page).to have_selector('#tab-issues li.issue-row', count: 4)
end
+
+ step 'I click link to remove milestone "v2.2"' do
+ click_link 'Remove'
+ end
+
+ step 'I should see no milestones' do
+ expect(page).to have_content('No milestones to show')
+ end
end
diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb
index 992cf2734fd..7a83d32a240 100644
--- a/features/steps/project/network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -11,8 +11,12 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
# Stub Graph max_size to speed up test (10 commits vs. 650)
Network::Graph.stub(max_count: 10)
- project = Project.find_by(name: "Shop")
- visit namespace_project_network_path(project.namespace, project, "master")
+ @project = Project.find_by(name: "Shop")
+ visit namespace_project_network_path(@project.namespace, @project, "master")
+ end
+
+ step "I visit project network page on branch 'test'" do
+ visit namespace_project_network_path(@project.namespace, @project, "'test'")
end
step 'page should select "master" in select box' do
@@ -29,6 +33,12 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end
end
+ step "page should have 'test' on graph" do
+ page.within '.network-graph' do
+ expect(page).to have_content "'test'"
+ end
+ end
+
When 'I switch ref to "feature"' do
select 'feature', from: 'ref'
sleep 2
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 95879b9544d..5cb085db207 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -193,6 +193,23 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive'))
end
+ step "I switch ref to 'test'" do
+ select "'test'", from: 'ref'
+ end
+
+ step "I see the ref 'test' has been selected" do
+ expect(page).to have_selector '.select2-chosen', text: "'test'"
+ end
+
+ step "I visit the 'test' tree" do
+ visit namespace_project_tree_path(@project.namespace, @project, "'test'")
+ end
+
+ step 'I see the commit data' do
+ expect(page).to have_css('.tree-commit-link', visible: true)
+ expect(page).not_to have_content('Loading commit data...')
+ end
+
private
def set_new_content
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 31202fa8c1f..b5556682449 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -6,6 +6,10 @@ module API
class UserBasic < UserSafe
expose :id, :state, :avatar_url
+
+ expose :web_url do |user, options|
+ Rails.application.routes.url_helpers.user_url(user)
+ end
end
class User < UserBasic
@@ -31,6 +35,10 @@ module API
expose :private_token
end
+ class Email < Grape::Entity
+ expose :id, :email
+ end
+
class Hook < Grape::Entity
expose :id, :url, :created_at
end
@@ -59,6 +67,7 @@ module API
expose :namespace
expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? }
expose :avatar_url
+ expose :star_count, :forks_count
end
class ProjectMember < UserBasic
@@ -69,6 +78,11 @@ module API
class Group < Grape::Entity
expose :id, :name, :path, :description
+ expose :avatar_url
+
+ expose :web_url do |group, options|
+ Rails.application.routes.url_helpers.group_url(group)
+ end
end
class GroupDetail < Group
@@ -171,6 +185,7 @@ module API
expose :source_project_id, :target_project_id
expose :label_names, as: :labels
expose :description
+ expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index e8b49fb7cba..7412274b045 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -233,7 +233,7 @@ module API
authorize! :read_merge_request, merge_request
- present paginate(merge_request.notes), with: Entities::MRNote
+ present paginate(merge_request.notes.fresh), with: Entities::MRNote
end
# Post comment to merge request
diff --git a/lib/api/users.rb b/lib/api/users.rb
index c468371d3d4..ee29f952246 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -131,11 +131,11 @@ module API
# Add ssh key to a specified user. Only available to admin users.
#
# Parameters:
- # id (required) - The ID of a user
- # key (required) - New SSH Key
- # title (required) - New SSH Key's title
+ # id (required) - The ID of a user
+ # key (required) - New SSH Key
+ # title (required) - New SSH Key's title
# Example Request:
- # POST /users/:id/keys
+ # POST /users/:id/keys
post ":id/keys" do
authenticated_as_admin!
required_attributes! [:title, :key]
@@ -153,9 +153,9 @@ module API
# Get ssh keys of a specified user. Only available to admin users.
#
# Parameters:
- # uid (required) - The ID of a user
+ # uid (required) - The ID of a user
# Example Request:
- # GET /users/:uid/keys
+ # GET /users/:uid/keys
get ':uid/keys' do
authenticated_as_admin!
user = User.find_by(id: params[:uid])
@@ -185,6 +185,65 @@ module API
end
end
+ # Add email to a specified user. Only available to admin users.
+ #
+ # Parameters:
+ # id (required) - The ID of a user
+ # email (required) - Email address
+ # Example Request:
+ # POST /users/:id/emails
+ post ":id/emails" do
+ authenticated_as_admin!
+ required_attributes! [:email]
+
+ user = User.find(params[:id])
+ attrs = attributes_for_keys [:email]
+ email = user.emails.new attrs
+ if email.save
+ NotificationService.new.new_email(email)
+ present email, with: Entities::Email
+ else
+ render_validation_error!(email)
+ end
+ end
+
+ # Get emails of a specified user. Only available to admin users.
+ #
+ # Parameters:
+ # uid (required) - The ID of a user
+ # Example Request:
+ # GET /users/:uid/emails
+ get ':uid/emails' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:uid])
+ not_found!('User') unless user
+
+ present user.emails, with: Entities::Email
+ end
+
+ # Delete existing email of a specified user. Only available to admin
+ # users.
+ #
+ # Parameters:
+ # uid (required) - The ID of a user
+ # id (required) - Email ID
+ # Example Request:
+ # DELETE /users/:uid/emails/:id
+ delete ':uid/emails/:id' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:uid])
+ not_found!('User') unless user
+
+ begin
+ email = user.emails.find params[:id]
+ email.destroy
+
+ user.update_secondary_emails!
+ rescue ActiveRecord::RecordNotFound
+ not_found!('Email')
+ end
+ end
+
# Delete user. Available only for admin
#
# Example Request:
@@ -289,6 +348,58 @@ module API
rescue
end
end
+
+ # Get currently authenticated user's emails
+ #
+ # Example Request:
+ # GET /user/emails
+ get "emails" do
+ present current_user.emails, with: Entities::Email
+ end
+
+ # Get single email owned by currently authenticated user
+ #
+ # Example Request:
+ # GET /user/emails/:id
+ get "emails/:id" do
+ email = current_user.emails.find params[:id]
+ present email, with: Entities::Email
+ end
+
+ # Add new email to currently authenticated user
+ #
+ # Parameters:
+ # email (required) - Email address
+ # Example Request:
+ # POST /user/emails
+ post "emails" do
+ required_attributes! [:email]
+
+ attrs = attributes_for_keys [:email]
+ email = current_user.emails.new attrs
+ if email.save
+ NotificationService.new.new_email(email)
+ present email, with: Entities::Email
+ else
+ render_validation_error!(email)
+ end
+ end
+
+ # Delete existing email of currently authenticated user
+ #
+ # Parameters:
+ # id (required) - EMail ID
+ # Example Request:
+ # DELETE /user/emails/:id
+ delete "emails/:id" do
+ begin
+ email = current_user.emails.find params[:id]
+ email.destroy
+
+ current_user.update_secondary_emails!
+ rescue
+ end
+ end
end
end
end
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index b8aa6b9ff2f..bbb230a10f0 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -7,7 +7,11 @@ module Backup
def initialize
@config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env]
@db_dir = File.join(Gitlab.config.backup.path, 'db')
- FileUtils.mkdir_p(@db_dir) unless Dir.exists?(@db_dir)
+ FileUtils.rm_rf(@db_dir)
+ # Ensure the parent dir of @db_dir exists
+ FileUtils.mkdir_p(Gitlab.config.backup.path)
+ # Fail if somebody raced to create @db_dir before us
+ FileUtils.mkdir(@db_dir, mode: 0700)
end
def dump
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 6fa2079d1a8..13c68d9354f 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -16,18 +16,16 @@ module Backup
file << s.to_yaml.gsub(/^---\n/,'')
end
- FileUtils.chmod(0700, folders_to_backup)
-
# create archive
$progress.print "Creating backup archive: #{tar_file} ... "
- orig_umask = File.umask(0077)
- if Kernel.system('tar', '-cf', tar_file, *backup_contents)
+ # Set file permissions on open to prevent chmod races.
+ tar_system_options = {out: [tar_file, 'w', Gitlab.config.backup.archive_permissions]}
+ if Kernel.system('tar', '-cf', '-', *backup_contents, tar_system_options)
$progress.puts "done".green
else
puts "creating archive #{tar_file} failed".red
abort 'Backup failed'
end
- File.umask(orig_umask)
upload(tar_file)
end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index dfb2da9f84e..4d70f7883dd 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -130,7 +130,10 @@ module Backup
def prepare
FileUtils.rm_rf(backup_repos_path)
- FileUtils.mkdir_p(backup_repos_path)
+ # Ensure the parent dir of backup_repos_path exists
+ FileUtils.mkdir_p(Gitlab.config.backup.path)
+ # Fail if somebody raced to create backup_repos_path before us
+ FileUtils.mkdir(backup_repos_path, mode: 0700)
end
def silent
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index bf43610acf6..1f9626644e6 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -10,7 +10,11 @@ module Backup
# Copy uploads from public/uploads to backup/uploads
def dump
- FileUtils.mkdir_p(backup_uploads_dir)
+ FileUtils.rm_rf(backup_uploads_dir)
+ # Ensure the parent dir of backup_uploads_dir exists
+ FileUtils.mkdir_p(Gitlab.config.backup.path)
+ # Fail if somebody raced to create backup_uploads_dir before us
+ FileUtils.mkdir(backup_uploads_dir, mode: 0700)
FileUtils.cp_r(app_uploads_dir, backup_dir)
end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 03cef30c97d..12292f614e9 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -26,7 +26,12 @@ module Grack
auth!
if project && authorized_request?
- @app.call(env)
+ if ENV['GITLAB_GRACK_AUTH_ONLY'] == '1'
+ # Tell gitlab-git-http-server the request is OK, and what the GL_ID is
+ render_grack_auth_ok
+ else
+ @app.call(env)
+ end
elsif @user.nil? && !@gitlab_ci
unauthorized
else
@@ -174,6 +179,10 @@ module Grack
end
end
+ def render_grack_auth_ok
+ [200, { "Content-Type" => "application/json" }, [JSON.dump({ 'GL_ID' => Gitlab::ShellEnv.gl_id(@user) })]]
+ end
+
def render_not_found
[404, { "Content-Type" => "text/plain" }, ["Not Found"]]
end
diff --git a/lib/gitlab/backend/shell_env.rb b/lib/gitlab/backend/shell_env.rb
index 17ec029eed4..9f5adee594a 100644
--- a/lib/gitlab/backend/shell_env.rb
+++ b/lib/gitlab/backend/shell_env.rb
@@ -7,7 +7,7 @@ module Gitlab
def set_env(user)
# Set GL_ID env variable
if user
- ENV['GL_ID'] = "user-#{user.id}"
+ ENV['GL_ID'] = gl_id(user)
end
end
@@ -15,5 +15,14 @@ module Gitlab
# Reset GL_ID env variable
ENV['GL_ID'] = nil
end
+
+ def gl_id(user)
+ if user.present?
+ "user-#{user.id}"
+ else
+ # This empty string is used in the render_grack_auth_ok method
+ ""
+ end
+ end
end
end
diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb
index 5b1952b9675..778b76f6890 100644
--- a/lib/gitlab/bitbucket_import/client.rb
+++ b/lib/gitlab/bitbucket_import/client.rb
@@ -1,6 +1,8 @@
module Gitlab
module BitbucketImport
class Client
+ class Unauthorized < StandardError; end
+
attr_reader :consumer, :api
def initialize(access_token = nil, access_token_secret = nil)
@@ -46,23 +48,23 @@ module Gitlab
end
def user
- JSON.parse(api.get("/api/1.0/user").body)
+ JSON.parse(get("/api/1.0/user").body)
end
def issues(project_identifier)
- JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues").body)
+ JSON.parse(get("/api/1.0/repositories/#{project_identifier}/issues").body)
end
def issue_comments(project_identifier, issue_id)
- JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues/#{issue_id}/comments").body)
+ JSON.parse(get("/api/1.0/repositories/#{project_identifier}/issues/#{issue_id}/comments").body)
end
def project(project_identifier)
- JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}").body)
+ JSON.parse(get("/api/1.0/repositories/#{project_identifier}").body)
end
def find_deploy_key(project_identifier, key)
- JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/deploy-keys").body).find do |deploy_key|
+ JSON.parse(get("/api/1.0/repositories/#{project_identifier}/deploy-keys").body).find do |deploy_key|
deploy_key["key"].chomp == key.chomp
end
end
@@ -82,11 +84,18 @@ module Gitlab
end
def projects
- JSON.parse(api.get("/api/1.0/user/repositories").body).select { |repo| repo["scm"] == "git" }
+ JSON.parse(get("/api/1.0/user/repositories").body).select { |repo| repo["scm"] == "git" }
end
private
+ def get(url)
+ response = api.get(url)
+ raise Unauthorized if (400..499).include?(response.code.to_i)
+
+ response
+ end
+
def config
Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket"}
end
diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb
index 70bfe059776..03c410726a5 100644
--- a/lib/gitlab/google_code_import/importer.rb
+++ b/lib/gitlab/google_code_import/importer.rb
@@ -327,7 +327,7 @@ module Gitlab
link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{filename}"
text = "[#{filename}](#{link})"
- text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/
+ text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/i
text
end.compact
end
diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb
index 9de2b24a9da..30f50b82996 100644
--- a/lib/gitlab/markdown/relative_link_filter.rb
+++ b/lib/gitlab/markdown/relative_link_filter.rb
@@ -98,15 +98,25 @@ module Gitlab
#
# Returns a String
def path_type(path)
- if repository.tree(current_sha, path).entries.any?
+ unescaped_path = Addressable::URI.unescape(path)
+
+ if tree?(unescaped_path)
'tree'
- elsif repository.blob_at(current_sha, path).try(:image?)
+ elsif image?(unescaped_path)
'raw'
else
'blob'
end
end
+ def tree?(path)
+ repository.tree(current_sha, path).entries.any?
+ end
+
+ def image?(path)
+ repository.blob_at(current_sha, path).try(:image?)
+ end
+
def current_sha
context[:commit].try(:id) ||
ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha
diff --git a/lib/gitlab/o_auth/provider.rb b/lib/gitlab/o_auth/provider.rb
index f986499a27c..90c3fe8da33 100644
--- a/lib/gitlab/o_auth/provider.rb
+++ b/lib/gitlab/o_auth/provider.rb
@@ -1,18 +1,30 @@
module Gitlab
module OAuth
class Provider
- def self.names
- providers = []
+ def self.providers
+ Devise.omniauth_providers
+ end
- Gitlab.config.ldap.servers.values.each do |server|
- providers << server['provider_name']
- end
+ def self.enabled?(name)
+ providers.include?(name.to_sym)
+ end
- Gitlab.config.omniauth.providers.each do |provider|
- providers << provider['name']
+ def self.ldap_provider?(name)
+ name.to_s.start_with?('ldap')
+ end
+
+ def self.config_for(name)
+ name = name.to_s
+ if ldap_provider?(name)
+ Gitlab::LDAP::Config.new(name).options
+ else
+ Gitlab.config.omniauth.providers.find { |provider| provider.name == name }
end
+ end
- providers
+ def self.label_for(name)
+ config = config_for(name)
+ (config && config['label']) || name.to_s.titleize
end
end
end
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index 2f7aff03c2a..04440e4f68d 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -22,10 +22,10 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
ERB::Util.html_escape_once(text)
end
- # Stolen from Rugments::Plugins::Redcarpet as this module is not required
- # from Rugments's gem root.
+ # Stolen from Rouge::Plugins::Redcarpet as this module is not required
+ # from Rouge's gem root.
def block_code(code, language)
- lexer = Rugments::Lexer.find_fancy(language, code) || Rugments::Lexers::PlainText
+ lexer = Rouge::Lexer.find_fancy(language, code) || Rouge::Lexers::PlainText
# XXX HACK: Redcarpet strips hard tabs out of code blocks,
# so we assume you're not using leading spaces that aren't tabs,
@@ -34,7 +34,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
code.gsub!(/^ /, "\t")
end
- formatter = Rugments::Formatters::HTML.new(
+ formatter = Rouge::Formatters::HTMLGitlab.new(
cssclass: "code highlight #{@color_scheme} #{lexer.tag}"
)
formatter.format(lexer.lex(code))
diff --git a/lib/repository_cache.rb b/lib/repository_cache.rb
index fa016a170cd..8ddc3511293 100644
--- a/lib/repository_cache.rb
+++ b/lib/repository_cache.rb
@@ -18,4 +18,12 @@ class RepositoryCache
def fetch(key, &block)
backend.fetch(cache_key(key), &block)
end
+
+ def exist?(key)
+ backend.exist?(cache_key(key))
+ end
+
+ def read(key)
+ backend.read(cache_key(key))
+ end
end
diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb
new file mode 100644
index 00000000000..092a920a0c4
--- /dev/null
+++ b/lib/rouge/formatters/html_gitlab.rb
@@ -0,0 +1,176 @@
+require 'cgi'
+
+module Rouge
+ module Formatters
+ class HTMLGitlab < Rouge::Formatter
+ tag 'html_gitlab'
+
+ # Creates a new <tt>Rouge::Formatter::HTMLGitlab</tt> instance.
+ #
+ # [+nowrap+] If set to True, don't wrap the output at all, not
+ # even inside a <tt><pre></tt> tag (default: false).
+ # [+cssclass+] CSS class for the wrapping <tt><div></tt> tag
+ # (default: 'highlight').
+ # [+linenos+] If set to 'table', output line numbers as a table
+ # with two cells, one containing the line numbers,
+ # the other the whole code. This is copy paste friendly,
+ # but may cause alignment problems with some browsers
+ # or fonts. If set to 'inline', the line numbers will
+ # be integrated in the <tt><pre></tt> tag that contains
+ # the code (default: nil).
+ # [+linenostart+] The line number for the first line (default: 1).
+ # [+lineanchors+] If set to true the formatter will wrap each output
+ # line in an anchor tag with a name of L-linenumber.
+ # This allows easy linking to certain lines
+ # (default: false).
+ # [+lineanchorsid+] If lineanchors is true the name of the anchors can
+ # be changed with lineanchorsid to e.g. foo-linenumber
+ # (default: 'L').
+ # [+anchorlinenos+] If set to true, will wrap line numbers in <tt><a></tt>
+ # tags. Used in combination with linenos and lineanchors
+ # (default: false).
+ # [+inline_theme+] Inline CSS styles for the <pre> tag (default: false).
+ def initialize(
+ nowrap: false,
+ cssclass: 'highlight',
+ linenos: nil,
+ linenostart: 1,
+ lineanchors: false,
+ lineanchorsid: 'L',
+ anchorlinenos: false,
+ inline_theme: nil
+ )
+ @nowrap = nowrap
+ @cssclass = cssclass
+ @linenos = linenos
+ @linenostart = linenostart
+ @lineanchors = lineanchors
+ @lineanchorsid = lineanchorsid
+ @anchorlinenos = anchorlinenos
+ @inline_theme = Theme.find(@inline_theme).new if @inline_theme.is_a?(String)
+ end
+
+ def render(tokens)
+ case @linenos
+ when 'table'
+ render_tableized(tokens)
+ when 'inline'
+ render_untableized(tokens)
+ else
+ render_untableized(tokens)
+ end
+ end
+
+ alias_method :format, :render
+
+ private
+
+ def render_untableized(tokens)
+ data = process_tokens(tokens)
+
+ html = ''
+ html << "<pre class=\"#{@cssclass}\"><code>" unless @nowrap
+ html << wrap_lines(data[:code])
+ html << "</code></pre>\n" unless @nowrap
+ html
+ end
+
+ def render_tableized(tokens)
+ data = process_tokens(tokens)
+
+ html = ''
+ html << "<div class=\"#{@cssclass}\">" unless @nowrap
+ html << '<table><tbody>'
+ html << "<td class=\"linenos\"><pre>"
+ html << wrap_linenos(data[:numbers])
+ html << '</pre></td>'
+ html << "<td class=\"lines\"><pre><code>"
+ html << wrap_lines(data[:code])
+ html << '</code></pre></td>'
+ html << '</tbody></table>'
+ html << '</div>' unless @nowrap
+ html
+ end
+
+ def process_tokens(tokens)
+ num_lines = 0
+ last_val = ''
+ rendered = ''
+
+ tokens.each do |tok, val|
+ last_val = val
+ num_lines += val.scan(/\n/).size
+ rendered << span(tok, val)
+ end
+
+ numbers = (@linenostart..num_lines + @linenostart - 1).to_a
+
+ { numbers: numbers, code: rendered }
+ end
+
+ def wrap_linenos(numbers)
+ if @anchorlinenos
+ numbers.map! do |number|
+ "<a href=\"##{@lineanchorsid}#{number}\">#{number}</a>"
+ end
+ end
+ numbers.join("\n")
+ end
+
+ def wrap_lines(rendered)
+ if @lineanchors
+ lines = rendered.split("\n")
+ lines = lines.each_with_index.map do |line, index|
+ number = index + @linenostart
+
+ if @linenos == 'inline'
+ "<a name=\"L#{number}\"></a>" \
+ "<span class=\"linenos\">#{number}</span>" \
+ "<span id=\"#{@lineanchorsid}#{number}\" class=\"line\">#{line}" \
+ '</span>'
+ else
+ "<span id=\"#{@lineanchorsid}#{number}\" class=\"line\">#{line}" \
+ '</span>'
+ end
+ end
+ lines.join("\n")
+ else
+ if @linenos == 'inline'
+ lines = rendered.split("\n")
+ lines = lines.each_with_index.map do |line, index|
+ number = index + @linenostart
+ "<span class=\"linenos\">#{number}</span>#{line}"
+ end
+ lines.join("\n")
+ else
+ rendered
+ end
+ end
+ end
+
+ def wrap_values(val, element)
+ lines = val.split("\n")
+ lines = lines.map{ |x| "<span #{element}>#{x}</span>" }
+ lines.join("\n")
+ end
+
+ def span(tok, val)
+ # http://stackoverflow.com/a/1600584/2587286
+ val = CGI.escapeHTML(val)
+
+ if tok.shortname.empty?
+ val
+ else
+ # In the case of multi-line values (e.g. comments), we need to apply
+ # styling to each line since span elements are inline.
+ if @inline_theme
+ rules = @inline_theme.style_for(tok).rendered_rules
+ wrap_values(val, "style=\"#{rules.to_a.join(';')}\"")
+ else
+ wrap_values(val, "class=\"#{tok.shortname}\"")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index 946902e2f6d..a3455728a94 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -41,7 +41,7 @@ shell_path="/bin/bash"
test -f /etc/default/gitlab && . /etc/default/gitlab
# Switch to the app_user if it is not he/she who is running the script.
-if [ "$USER" != "$app_user" ]; then
+if [ `whoami` != "$app_user" ]; then
eval su - "$app_user" -s $shell_path -c $(echo \")$0 "$@"$(echo \"); exit;
fi
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index edb987875df..efa0898900f 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -38,6 +38,11 @@ upstream gitlab {
server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
}
+## Experimental: gitlab-git-http-server
+# upstream gitlab-git-http-server {
+# server localhost:8181;
+# }
+
## Normal HTTP host
server {
## Either remove "default_server" from the listen line below,
@@ -109,6 +114,26 @@ server {
proxy_pass http://gitlab;
}
+ ## Experimental: send Git HTTP traffic to gitlab-git-http-server instead of Unicorn
+ # location ~ [-\/\w\.]+\.git\/ {
+ # ## If you use HTTPS make sure you disable gzip compression
+ # ## to be safe against BREACH attack.
+ # # gzip off;
+
+ # ## https://github.com/gitlabhq/gitlabhq/issues/694
+ # ## Some requests take more than 30 seconds.
+ # proxy_read_timeout 300;
+ # proxy_connect_timeout 300;
+ # proxy_redirect off;
+
+ # proxy_set_header Host $http_host;
+ # proxy_set_header X-Real-IP $remote_addr;
+ # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # proxy_set_header X-Forwarded-Proto $scheme;
+
+ # proxy_pass http://gitlab-git-http-server;
+ # }
+
## Enable gzip compression as per rails guide:
## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression
## WARNING: If you are using relative urls remove the block below
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 766559b49f6..314525518f1 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -42,6 +42,11 @@ upstream gitlab {
server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
}
+## Experimental: gitlab-git-http-server
+# upstream gitlab-git-http-server {
+# server localhost:8181;
+# }
+
## Redirects all HTTP traffic to the HTTPS host
server {
## Either remove "default_server" from the listen line below,
@@ -156,6 +161,26 @@ server {
proxy_pass http://gitlab;
}
+ ## Experimental: send Git HTTP traffic to gitlab-git-http-server instead of Unicorn
+ # location ~ [-\/\w\.]+\.git\/ {
+ # ## If you use HTTPS make sure you disable gzip compression
+ # ## to be safe against BREACH attack.
+ # gzip off;
+
+ # ## https://github.com/gitlabhq/gitlabhq/issues/694
+ # ## Some requests take more than 30 seconds.
+ # proxy_read_timeout 300;
+ # proxy_connect_timeout 300;
+ # proxy_redirect off;
+
+ # proxy_set_header Host $http_host;
+ # proxy_set_header X-Real-IP $remote_addr;
+ # proxy_set_header X-Forwarded-Ssl on;
+ # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # proxy_set_header X-Forwarded-Proto $scheme;
+ # proxy_pass http://gitlab-git-http-server;
+ # }
+
## Enable gzip compression as per rails guide:
## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression
## WARNING: If you are using relative urls remove the block below
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 21f159e1bfe..8acb6a7fd19 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -429,7 +429,8 @@ namespace :gitlab do
if project.empty_repo?
puts "repository is empty".magenta
- elsif File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path)
+ elsif File.directory?(project_hook_directory) && File.directory?(gitlab_shell_hooks_path) &&
+ (File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path))
puts 'ok'.green
else
puts "wrong or missing hooks".red
@@ -698,7 +699,7 @@ namespace :gitlab do
print "Ruby version >= #{required_version} ? ... "
if current_version.valid? && required_version <= current_version
- puts "yes (#{current_version})".green
+ puts "yes (#{current_version})".green
else
puts "no".red
try_fixing_it(
@@ -716,7 +717,7 @@ namespace :gitlab do
print "Git version >= #{required_version} ? ... "
if current_version.valid? && required_version <= current_version
- puts "yes (#{current_version})".green
+ puts "yes (#{current_version})".green
else
puts "no".red
try_fixing_it(
@@ -750,4 +751,3 @@ namespace :gitlab do
end
end
end
-
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index 5f83e5e8e7f..c1ee271ae2b 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -62,11 +62,11 @@ namespace :gitlab do
project = Projects::CreateService.new(user, project_params).execute
- if project.valid?
+ if project.persisted?
puts " * Created #{project.name} (#{repo_path})".green
else
puts " * Failed trying to create #{project.name} (#{repo_path})".red
- puts " Validation Errors: #{project.errors.messages}".red
+ puts " Errors: #{project.errors.messages}".red
end
end
end
diff --git a/lib/tasks/gitlab/mail_google_schema_whitelisting.rake b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake
deleted file mode 100644
index 102c6ae55d5..00000000000
--- a/lib/tasks/gitlab/mail_google_schema_whitelisting.rake
+++ /dev/null
@@ -1,73 +0,0 @@
-require "#{Rails.root}/app/helpers/emails_helper"
-require 'action_view/helpers'
-extend ActionView::Helpers
-
-include ActionView::Context
-include EmailsHelper
-
-namespace :gitlab do
- desc "Email google whitelisting email with example email for actions in inbox"
- task mail_google_schema_whitelisting: :environment do
- subject = "Rails | Implemented feature"
- url = "#{Gitlab.config.gitlab.url}/base/rails-project/issues/#{rand(1..100)}#note_#{rand(10..1000)}"
- schema = email_action(url)
- body = email_template(schema, url)
- mail = Notify.test_email("schema.whitelisting+sample@gmail.com", subject, body.html_safe)
- if send_now
- mail.deliver
- else
- puts "WOULD SEND:"
- end
- puts mail
- end
-
- def email_template(schema, url)
- "<html lang='en'>
- <head>
- <meta content='text/html; charset=utf-8' http-equiv='Content-Type'>
- <title>
- GitLab
- </title>
- </meta>
- </head>
- <style>
- img {
- max-width: 100%;
- height: auto;
- }
- p.details {
- font-style:italic;
- color:#777
- }
- .footer p {
- font-size:small;
- color:#777
- }
- </style>
- <body>
- <div class='content'>
- <div>
- <p>I like it :+1: </p>
- </div>
- </div>
-
- <div class='footer' style='margin-top: 10px;'>
- <p>
- <br>
- <a href=\"#{url}\">View it on GitLab</a>
- You're receiving this notification because you are a member of the Base / Rails Project project team.
- #{schema}
- </p>
- </div>
- </body>
- </html>"
- end
-
- def send_now
- if ENV['SEND'] == "true"
- true
- else
- false
- end
- end
-end
diff --git a/lib/tasks/gitlab/update_commit_count.rake b/lib/tasks/gitlab/update_commit_count.rake
new file mode 100644
index 00000000000..9b636f12d9f
--- /dev/null
+++ b/lib/tasks/gitlab/update_commit_count.rake
@@ -0,0 +1,20 @@
+namespace :gitlab do
+ desc "GitLab | Update commit count for projects"
+ task update_commit_count: :environment do
+ projects = Project.where(commit_count: 0)
+ puts "#{projects.size} projects need to be updated. This might take a while."
+ ask_to_continue unless ENV['force'] == 'yes'
+
+ projects.find_each(batch_size: 100) do |project|
+ print "#{project.name_with_namespace.yellow} ... "
+
+ unless project.repo_exists?
+ puts "skipping, because the repo is empty".magenta
+ next
+ end
+
+ project.update_commit_count
+ puts project.commit_count.to_s.green
+ end
+ end
+end
diff --git a/lib/unfold_form.rb b/lib/unfold_form.rb
index 46b12beeaaf..fcd01503d1b 100644
--- a/lib/unfold_form.rb
+++ b/lib/unfold_form.rb
@@ -8,4 +8,5 @@ class UnfoldForm
attribute :bottom, Boolean
attribute :unfold, Boolean, default: true
attribute :offset, Integer
+ attribute :indent, Integer, default: 0
end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
new file mode 100644
index 00000000000..d3868c13202
--- /dev/null
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Projects::MilestonesController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:issue) { create(:issue, project: project, milestone: milestone) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+ controller.instance_variable_set(:@project, project)
+ end
+
+ describe "#destroy" do
+ it "should remove milestone" do
+ expect(issue.milestone_id).to eq(milestone.id)
+ delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.id, format: :js
+ expect(response).to be_success
+ expect { Milestone.find(milestone.id) }.to raise_exception(ActiveRecord::RecordNotFound)
+ issue.reload
+ expect(issue.milestone_id).to eq(nil)
+ # Check system note left for milestone removal
+ last_note = project.issues.find(issue.id).notes[-1].note
+ expect(last_note).to eq('Milestone removed')
+ end
+ end
+end
diff --git a/spec/controllers/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index e09caf5df13..53915856357 100644
--- a/spec/controllers/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -8,9 +8,6 @@ describe Projects::TreeController do
sign_in(user)
project.team << [user, :master]
-
- allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz'])
- allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0'])
controller.instance_variable_set(:@project, project)
end
@@ -44,6 +41,32 @@ describe Projects::TreeController do
let(:id) { 'invalid-branch/encoding/' }
it { is_expected.to respond_with(:not_found) }
end
+
+ context "valid empty branch, invalid path" do
+ let(:id) { 'empty-branch/invalid-path/' }
+ it { is_expected.to respond_with(:not_found) }
+ end
+
+ context "valid empty branch" do
+ let(:id) { 'empty-branch' }
+ it { is_expected.to respond_with(:success) }
+ end
+
+ context "invalid SHA commit ID" do
+ let(:id) { 'ff39438/.gitignore' }
+ it { is_expected.to respond_with(:not_found) }
+ end
+
+ context "valid SHA commit ID" do
+ let(:id) { '6d39438' }
+ it { is_expected.to respond_with(:success) }
+ end
+
+ context "valid SHA commit ID with path" do
+ let(:id) { '6d39438/.gitignore' }
+ it { expect(response.status).to eq(302) }
+ end
+
end
describe 'GET show with blob path' do
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index d47a37914df..9f89101d7f7 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -1,25 +1,38 @@
require 'spec_helper'
describe UsersController do
- let(:user) { create(:user, username: 'user1', name: 'User 1', email: 'user1@gitlab.com') }
-
- before do
- sign_in(user)
- end
+ let(:user) { create(:user) }
describe 'GET #show' do
- render_views
+ it 'is case-insensitive' do
+ user = create(:user, username: 'CamelCaseUser')
+ sign_in(user)
+
+ get :show, username: user.username.downcase
- it 'renders the show template' do
- get :show, username: user.username
- expect(response.status).to eq(200)
- expect(response).to render_template('show')
+ expect(response).to be_success
+ end
+
+ context 'with rendered views' do
+ render_views
+
+ it 'renders the show template' do
+ sign_in(user)
+
+ get :show, username: user.username
+
+ expect(response).to be_success
+ expect(response).to render_template('show')
+ end
end
end
describe 'GET #calendar' do
it 'renders calendar' do
+ sign_in(user)
+
get :calendar, username: user.username
+
expect(response).to render_template('calendar')
end
end
@@ -30,6 +43,8 @@ describe UsersController do
before do
allow_any_instance_of(User).to receive(:contributed_projects_ids).and_return([project.id])
+
+ sign_in(user)
project.team << [user, :developer]
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 102678a1d74..1d500a11ad7 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -21,12 +21,13 @@
# import_url :string(255)
# visibility_level :integer default(0), not null
# archived :boolean default(FALSE), not null
+# avatar :string(255)
# import_status :string(255)
# repository_size :float default(0.0)
# star_count :integer default(0), not null
# import_type :string(255)
# import_source :string(255)
-# avatar :string(255)
+# commit_count :integer default(0)
#
FactoryGirl.define do
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index b8199aa5e61..859a62f740f 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -17,410 +17,215 @@ require 'erb'
# -> Post-process HTML
# -> `gfm_with_options` helper
# -> HTML::Pipeline
-# -> Sanitize
-# -> RelativeLink
-# -> Emoji
-# -> Table of Contents
-# -> Autolinks
-# -> Rinku (http, https, ftp)
-# -> Other schemes
-# -> ExternalLink
-# -> References
-# -> TaskList
+# -> SanitizationFilter
+# -> Other filters, depending on pipeline
# -> `html_safe`
# -> Template
#
# See the MarkdownFeature class for setup details.
describe 'GitLab Markdown', feature: true do
- include ActionView::Helpers::TagHelper
- include ActionView::Helpers::UrlHelper
include Capybara::Node::Matchers
include GitlabMarkdownHelper
+ include MarkdownMatchers
- # `markdown` calls these two methods
- def current_user
- @feat.user
- end
-
- def user_color_scheme_class
- :white
- end
-
- # Let's only parse this thing once
- before(:all) do
- @feat = MarkdownFeature.new
-
- # `markdown` expects a `@project` variable
- @project = @feat.project
-
- @md = markdown(@feat.raw_markdown)
- @doc = Nokogiri::HTML::DocumentFragment.parse(@md)
- end
-
- after(:all) do
- @feat.teardown
+ # Sometimes it can be useful to see the parsed output of the Markdown document
+ # for debugging. Call this method to write the output to
+ # `tmp/capybara/<filename>.html`.
+ def write_markdown(filename = 'markdown_spec')
+ File.open(Rails.root.join("tmp/capybara/#{filename}.html"), 'w') do |file|
+ file.puts @html
+ end
end
- # Given a header ID, goes to that element's parent (the header itself), then
- # its next sibling element (the body).
- def get_section(id)
- @doc.at_css("##{id}").parent.next_element
+ def doc(html = @html)
+ Nokogiri::HTML::DocumentFragment.parse(html)
end
- # Sometimes it can be useful to see the parsed output of the Markdown document
- # for debugging. Uncomment this block to write the output to
- # tmp/capybara/markdown_spec.html.
- #
- # it 'writes to a file' do
- # File.open(Rails.root.join('tmp/capybara/markdown_spec.html'), 'w') do |file|
- # file.puts @md
- # end
- # end
-
- describe 'Markdown' do
- describe 'No Intra Emphasis' do
+ # Shared behavior that all pipelines should exhibit
+ shared_examples 'all pipelines' do
+ describe 'Redcarpet extensions' do
it 'does not parse emphasis inside of words' do
- body = get_section('no-intra-emphasis')
- expect(body.to_html).not_to match('foo<em>bar</em>baz')
+ expect(doc.to_html).not_to match('foo<em>bar</em>baz')
end
- end
- describe 'Tables' do
it 'parses table Markdown' do
- body = get_section('tables')
- expect(body).to have_selector('th:contains("Header")')
- expect(body).to have_selector('th:contains("Row")')
- expect(body).to have_selector('th:contains("Example")')
+ aggregate_failures do
+ expect(doc).to have_selector('th:contains("Header")')
+ expect(doc).to have_selector('th:contains("Row")')
+ expect(doc).to have_selector('th:contains("Example")')
+ end
end
it 'allows Markdown in tables' do
- expect(@doc.at_css('td:contains("Baz")').children.to_html).
+ expect(doc.at_css('td:contains("Baz")').children.to_html).
to eq '<strong>Baz</strong>'
end
- end
- describe 'Fenced Code Blocks' do
it 'parses fenced code blocks' do
- expect(@doc).to have_selector('pre.code.highlight.white.c')
- expect(@doc).to have_selector('pre.code.highlight.white.python')
+ aggregate_failures do
+ expect(doc).to have_selector('pre.code.highlight.white.c')
+ expect(doc).to have_selector('pre.code.highlight.white.python')
+ end
end
- end
- describe 'Strikethrough' do
it 'parses strikethroughs' do
- expect(@doc).to have_selector(%{del:contains("and this text doesn't")})
+ expect(doc).to have_selector(%{del:contains("and this text doesn't")})
end
- end
- describe 'Superscript' do
it 'parses superscript' do
- body = get_section('superscript')
- expect(body.to_html).to match('1<sup>st</sup>')
- expect(body.to_html).to match('2<sup>nd</sup>')
+ expect(doc).to have_selector('sup', count: 2)
end
end
- end
- describe 'HTML::Pipeline' do
describe 'SanitizationFilter' do
- it 'uses a permissive whitelist' do
- expect(@doc).to have_selector('b:contains("b tag")')
- expect(@doc).to have_selector('em:contains("em tag")')
- expect(@doc).to have_selector('code:contains("code tag")')
- expect(@doc).to have_selector('kbd:contains("s")')
- expect(@doc).to have_selector('strike:contains(Emoji)')
- expect(@doc).to have_selector('img[src*="smile.png"]')
- expect(@doc).to have_selector('br')
- expect(@doc).to have_selector('hr')
+ it 'permits b elements' do
+ expect(doc).to have_selector('b:contains("b tag")')
end
- it 'permits span elements' do
- expect(@doc).to have_selector('span:contains("span tag")')
+ it 'permits em elements' do
+ expect(doc).to have_selector('em:contains("em tag")')
end
- it 'permits table alignment' do
- expect(@doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center'
- expect(@doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right'
- expect(@doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left'
-
- expect(@doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center'
- expect(@doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right'
- expect(@doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left'
+ it 'permits code elements' do
+ expect(doc).to have_selector('code:contains("code tag")')
end
- it 'removes `rel` attribute from links' do
- body = get_section('sanitizationfilter')
- expect(body).not_to have_selector('a[rel="bookmark"]')
+ it 'permits kbd elements' do
+ expect(doc).to have_selector('kbd:contains("s")')
end
- it "removes `href` from `a` elements if it's fishy" do
- expect(@doc).not_to have_selector('a[href*="javascript"]')
+ it 'permits strike elements' do
+ expect(doc).to have_selector('strike:contains(Emoji)')
end
- end
- describe 'Escaping' do
- let(:table) { @doc.css('table').last.at_css('tbody') }
-
- it 'escapes non-tag angle brackets' do
- expect(table.at_xpath('.//tr[1]/td[3]').inner_html).to eq '1 &lt; 3 &amp; 5'
+ it 'permits img elements' do
+ expect(doc).to have_selector('img[src*="smile.png"]')
end
- end
-
- describe 'Edge Cases' do
- it 'allows markup inside link elements' do
- expect(@doc.at_css('a[href="#link-emphasis"]').to_html).
- to eq %{<a href="#link-emphasis"><em>text</em></a>}
-
- expect(@doc.at_css('a[href="#link-strong"]').to_html).
- to eq %{<a href="#link-strong"><strong>text</strong></a>}
- expect(@doc.at_css('a[href="#link-code"]').to_html).
- to eq %{<a href="#link-code"><code>text</code></a>}
+ it 'permits br elements' do
+ expect(doc).to have_selector('br')
end
- end
- describe 'EmojiFilter' do
- it 'parses Emoji' do
- expect(@doc).to have_selector('img.emoji', count: 10)
+ it 'permits hr elements' do
+ expect(doc).to have_selector('hr')
end
- end
- describe 'TableOfContentsFilter' do
- it 'creates anchors inside header elements' do
- expect(@doc).to have_selector('h1 a#gitlab-markdown')
- expect(@doc).to have_selector('h2 a#markdown')
- expect(@doc).to have_selector('h3 a#autolinkfilter')
+ it 'permits span elements' do
+ expect(doc).to have_selector('span:contains("span tag")')
end
- end
- describe 'AutolinkFilter' do
- let(:list) { get_section('autolinkfilter').next_element }
-
- def item(index)
- list.at_css("li:nth-child(#{index})")
+ it 'permits style attribute in th elements' do
+ aggregate_failures do
+ expect(doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center'
+ expect(doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right'
+ expect(doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left'
+ end
end
- it 'autolinks http://' do
- expect(item(1).children.first.name).to eq 'a'
- expect(item(1).children.first['href']).to eq 'http://about.gitlab.com/'
+ it 'permits style attribute in td elements' do
+ aggregate_failures do
+ expect(doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center'
+ expect(doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right'
+ expect(doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left'
+ end
end
- it 'autolinks https://' do
- expect(item(2).children.first.name).to eq 'a'
- expect(item(2).children.first['href']).to eq 'https://google.com/'
+ it 'removes `rel` attribute from links' do
+ expect(doc).not_to have_selector('a[rel="bookmark"]')
end
- it 'autolinks ftp://' do
- expect(item(3).children.first.name).to eq 'a'
- expect(item(3).children.first['href']).to eq 'ftp://ftp.us.debian.org/debian/'
+ it "removes `href` from `a` elements if it's fishy" do
+ expect(doc).not_to have_selector('a[href*="javascript"]')
end
+ end
- it 'autolinks smb://' do
- expect(item(4).children.first.name).to eq 'a'
- expect(item(4).children.first['href']).to eq 'smb://foo/bar/baz'
+ describe 'Escaping' do
+ it 'escapes non-tag angle brackets' do
+ table = doc.css('table').last.at_css('tbody')
+ expect(table.at_xpath('.//tr[1]/td[3]').inner_html).to eq '1 &lt; 3 &amp; 5'
end
+ end
- it 'autolinks irc://' do
- expect(item(5).children.first.name).to eq 'a'
- expect(item(5).children.first['href']).to eq 'irc://irc.freenode.net/git'
- end
+ describe 'Edge Cases' do
+ it 'allows markup inside link elements' do
+ aggregate_failures do
+ expect(doc.at_css('a[href="#link-emphasis"]').to_html).
+ to eq %{<a href="#link-emphasis"><em>text</em></a>}
- it 'autolinks short, invalid URLs' do
- expect(item(6).children.first.name).to eq 'a'
- expect(item(6).children.first['href']).to eq 'http://localhost:3000'
- end
+ expect(doc.at_css('a[href="#link-strong"]').to_html).
+ to eq %{<a href="#link-strong"><strong>text</strong></a>}
- %w(code a kbd).each do |elem|
- it "ignores links inside '#{elem}' element" do
- body = get_section('autolinkfilter')
- expect(body).not_to have_selector("#{elem} a")
+ expect(doc.at_css('a[href="#link-code"]').to_html).
+ to eq %{<a href="#link-code"><code>text</code></a>}
end
end
end
describe 'ExternalLinkFilter' do
- let(:links) { get_section('externallinkfilter').next_element }
-
it 'adds nofollow to external link' do
- expect(links.css('a').first.to_html).to match 'nofollow'
+ link = doc.at_css('a:contains("Google")')
+ expect(link.attr('rel')).to match 'nofollow'
end
it 'ignores internal link' do
- expect(links.css('a').last.to_html).not_to match 'nofollow'
+ link = doc.at_css('a:contains("GitLab Root")')
+ expect(link.attr('rel')).not_to match 'nofollow'
end
end
+ end
- describe 'ReferenceFilter' do
- it 'handles references in headers' do
- header = @doc.at_css('#reference-filters-eg-1').parent
-
- expect(header.css('a').size).to eq 2
- end
-
- it "handles references in Markdown" do
- body = get_section('reference-filters-eg-1')
- expect(body).to have_selector('em a.gfm-merge_request', count: 1)
- end
-
- it 'parses user references' do
- body = get_section('userreferencefilter')
- expect(body).to have_selector('a.gfm.gfm-project_member', count: 3)
- end
-
- it 'parses issue references' do
- body = get_section('issuereferencefilter')
- expect(body).to have_selector('a.gfm.gfm-issue', count: 2)
- end
-
- it 'parses merge request references' do
- body = get_section('mergerequestreferencefilter')
- expect(body).to have_selector('a.gfm.gfm-merge_request', count: 2)
- end
+ context 'default pipeline' do
+ before(:all) do
+ @feat = MarkdownFeature.new
- it 'parses snippet references' do
- body = get_section('snippetreferencefilter')
- expect(body).to have_selector('a.gfm.gfm-snippet', count: 2)
- end
+ # `gfm_with_options` depends on a `@project` variable
+ @project = @feat.project
- it 'parses commit range references' do
- body = get_section('commitrangereferencefilter')
- expect(body).to have_selector('a.gfm.gfm-commit_range', count: 2)
- end
+ @html = markdown(@feat.raw_markdown)
+ end
- it 'parses commit references' do
- body = get_section('commitreferencefilter')
- expect(body).to have_selector('a.gfm.gfm-commit', count: 2)
- end
+ it_behaves_like 'all pipelines'
- it 'parses label references' do
- body = get_section('labelreferencefilter')
- expect(body).to have_selector('a.gfm.gfm-label', count: 3)
- end
+ it 'includes RelativeLinkFilter' do
+ expect(doc).to parse_relative_links
end
- describe 'Task Lists' do
- it 'generates task lists' do
- body = get_section('task-lists')
- expect(body).to have_selector('ul.task-list', count: 2)
- expect(body).to have_selector('li.task-list-item', count: 7)
- expect(body).to have_selector('input[checked]', count: 3)
- end
+ it 'includes EmojiFilter' do
+ expect(doc).to parse_emoji
end
- end
-end
-
-# This is a helper class used by the GitLab Markdown feature spec
-#
-# Because the feature spec only cares about the output of the Markdown, and the
-# test setup and teardown and parsing is fairly expensive, we only want to do it
-# once. Unfortunately RSpec will not let you access `let`s in a `before(:all)`
-# block, so we fake it by encapsulating all the shared setup in this class.
-#
-# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for
-# reference to the factory-created objects.
-class MarkdownFeature
- include FactoryGirl::Syntax::Methods
-
- def initialize
- DatabaseCleaner.start
- end
-
- def teardown
- DatabaseCleaner.clean
- end
-
- def user
- @user ||= create(:user)
- end
- def group
- unless @group
- @group = create(:group)
- @group.add_user(user, Gitlab::Access::DEVELOPER)
+ it 'includes TableOfContentsFilter' do
+ expect(doc).to create_header_links
end
- @group
- end
-
- # Direct references ----------------------------------------------------------
-
- def project
- @project ||= create(:project)
- end
-
- def issue
- @issue ||= create(:issue, project: project)
- end
-
- def merge_request
- @merge_request ||= create(:merge_request, :simple, source_project: project)
- end
-
- def snippet
- @snippet ||= create(:project_snippet, project: project)
- end
-
- def commit
- @commit ||= project.commit
- end
-
- def commit_range
- unless @commit_range
- commit2 = project.commit('HEAD~3')
- @commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
+ it 'includes AutolinkFilter' do
+ expect(doc).to create_autolinks
end
- @commit_range
- end
-
- def simple_label
- @simple_label ||= create(:label, name: 'gfm', project: project)
- end
-
- def label
- @label ||= create(:label, name: 'awaiting feedback', project: project)
- end
-
- # Cross-references -----------------------------------------------------------
-
- def xproject
- unless @xproject
- namespace = create(:namespace, name: 'cross-reference')
- @xproject = create(:project, namespace: namespace)
- @xproject.team << [user, :developer]
+ it 'includes all reference filters' do
+ aggregate_failures do
+ expect(doc).to reference_users
+ expect(doc).to reference_issues
+ expect(doc).to reference_merge_requests
+ expect(doc).to reference_snippets
+ expect(doc).to reference_commit_ranges
+ expect(doc).to reference_commits
+ expect(doc).to reference_labels
+ end
end
- @xproject
- end
-
- def xissue
- @xissue ||= create(:issue, project: xproject)
- end
-
- def xmerge_request
- @xmerge_request ||= create(:merge_request, :simple, source_project: xproject)
- end
-
- def xsnippet
- @xsnippet ||= create(:project_snippet, project: xproject)
- end
-
- def xcommit
- @xcommit ||= xproject.commit
- end
-
- def xcommit_range
- unless @xcommit_range
- xcommit2 = xproject.commit('HEAD~2')
- @xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
+ it 'includes TaskListFilter' do
+ expect(doc).to parse_task_lists
end
+ end
- @xcommit_range
+ # `markdown` calls these two methods
+ def current_user
+ @feat.user
end
- def raw_markdown
- fixture = Rails.root.join('spec/fixtures/markdown.md.erb')
- ERB.new(File.read(fixture)).result(binding)
+ def user_color_scheme_class
+ :white
end
end
diff --git a/spec/features/admin/security_spec.rb b/spec/features/security/admin_access_spec.rb
index 175fa9d4647..fe8cd7b7602 100644
--- a/spec/features/admin/security_spec.rb
+++ b/spec/features/security/admin_access_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe "Admin::Projects", feature: true do
+ include AccessMatchers
+
describe "GET /admin/projects" do
subject { admin_namespaces_projects_path }
diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb
index 67238e3ab76..c38cddbb904 100644
--- a/spec/features/security/dashboard_access_spec.rb
+++ b/spec/features/security/dashboard_access_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe "Dashboard access", feature: true do
+ include AccessMatchers
+
describe "GET /dashboard" do
subject { dashboard_path }
diff --git a/spec/features/security/group/group_access_spec.rb b/spec/features/security/group/group_access_spec.rb
deleted file mode 100644
index 63793149459..00000000000
--- a/spec/features/security/group/group_access_spec.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-require 'spec_helper'
-
-describe "Group access", feature: true do
- describe "GET /projects/new" do
- it { expect(new_group_path).to be_allowed_for :admin }
- it { expect(new_group_path).to be_allowed_for :user }
- it { expect(new_group_path).to be_denied_for :visitor }
- end
-
- describe "Group" do
- let(:group) { create(:group) }
-
- let(:owner) { create(:owner) }
- let(:master) { create(:user) }
- let(:reporter) { create(:user) }
- let(:guest) { create(:user) }
- let(:nonmember) { create(:user) }
-
- before do
- group.add_user(owner, Gitlab::Access::OWNER)
- group.add_user(master, Gitlab::Access::MASTER)
- group.add_user(reporter, Gitlab::Access::REPORTER)
- group.add_user(guest, Gitlab::Access::GUEST)
- end
-
- describe "GET /groups/:path" do
- subject { group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- describe "GET /groups/:path/issues" do
- subject { issues_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- describe "GET /groups/:path/merge_requests" do
- subject { merge_requests_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- describe "GET /groups/:path/group_members" do
- subject { group_group_members_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- describe "GET /groups/:path/edit" do
- subject { edit_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_denied_for master }
- it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- describe "GET /groups/:path/projects" do
- subject { projects_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_denied_for master }
- it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
- end
-end
diff --git a/spec/features/security/group/internal_group_access_spec.rb b/spec/features/security/group/internal_group_access_spec.rb
deleted file mode 100644
index d17a7412e43..00000000000
--- a/spec/features/security/group/internal_group_access_spec.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-require 'spec_helper'
-
-describe "Group with internal project access", feature: true do
- describe "Group" do
- let(:group) { create(:group) }
-
- let(:owner) { create(:owner) }
- let(:master) { create(:user) }
- let(:reporter) { create(:user) }
- let(:guest) { create(:user) }
- let(:nonmember) { create(:user) }
-
- before do
- group.add_user(owner, Gitlab::Access::OWNER)
- group.add_user(master, Gitlab::Access::MASTER)
- group.add_user(reporter, Gitlab::Access::REPORTER)
- group.add_user(guest, Gitlab::Access::GUEST)
-
- create(:project, :internal, group: group)
- end
-
- describe "GET /groups/:path" do
- subject { group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- describe "GET /groups/:path/issues" do
- subject { issues_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- describe "GET /groups/:path/merge_requests" do
- subject { merge_requests_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- describe "GET /groups/:path/group_members" do
- subject { group_group_members_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- describe "GET /groups/:path/edit" do
- subject { edit_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_denied_for master }
- it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
- end
-end
diff --git a/spec/features/security/group/mixed_group_access_spec.rb b/spec/features/security/group/mixed_group_access_spec.rb
deleted file mode 100644
index b3db7b5dea4..00000000000
--- a/spec/features/security/group/mixed_group_access_spec.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-require 'spec_helper'
-
-describe "Group access", feature: true do
- describe "Group" do
- let(:group) { create(:group) }
-
- let(:owner) { create(:owner) }
- let(:master) { create(:user) }
- let(:reporter) { create(:user) }
- let(:guest) { create(:user) }
- let(:nonmember) { create(:user) }
-
- before do
- group.add_user(owner, Gitlab::Access::OWNER)
- group.add_user(master, Gitlab::Access::MASTER)
- group.add_user(reporter, Gitlab::Access::REPORTER)
- group.add_user(guest, Gitlab::Access::GUEST)
-
- create(:project, :internal, path: "internal_project", group: group)
- create(:project, :public, path: "public_project", group: group)
- end
-
- describe "GET /groups/:path" do
- subject { group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- describe "GET /groups/:path/issues" do
- subject { issues_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- describe "GET /groups/:path/merge_requests" do
- subject { merge_requests_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- describe "GET /groups/:path/group_members" do
- subject { group_group_members_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- describe "GET /groups/:path/edit" do
- subject { edit_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_denied_for master }
- it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
- end
-end
diff --git a/spec/features/security/group/public_group_access_spec.rb b/spec/features/security/group/public_group_access_spec.rb
deleted file mode 100644
index c16f0c0d1e1..00000000000
--- a/spec/features/security/group/public_group_access_spec.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-require 'spec_helper'
-
-describe "Group with public project access", feature: true do
- describe "Group" do
- let(:group) { create(:group) }
-
- let(:owner) { create(:owner) }
- let(:master) { create(:user) }
- let(:reporter) { create(:user) }
- let(:guest) { create(:user) }
- let(:nonmember) { create(:user) }
-
- before do
- group.add_user(owner, Gitlab::Access::OWNER)
- group.add_user(master, Gitlab::Access::MASTER)
- group.add_user(reporter, Gitlab::Access::REPORTER)
- group.add_user(guest, Gitlab::Access::GUEST)
-
- create(:project, :public, group: group)
- end
-
- describe "GET /groups/:path" do
- subject { group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- describe "GET /groups/:path/issues" do
- subject { issues_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- describe "GET /groups/:path/merge_requests" do
- subject { merge_requests_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- describe "GET /groups/:path/group_members" do
- subject { group_group_members_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_allowed_for master }
- it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for guest }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- describe "GET /groups/:path/edit" do
- subject { edit_group_path(group) }
-
- it { is_expected.to be_allowed_for owner }
- it { is_expected.to be_denied_for master }
- it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
- end
-end
diff --git a/spec/features/security/group_access_spec.rb b/spec/features/security/group_access_spec.rb
new file mode 100644
index 00000000000..8ce15388605
--- /dev/null
+++ b/spec/features/security/group_access_spec.rb
@@ -0,0 +1,284 @@
+require 'rails_helper'
+
+describe 'Group access', feature: true do
+ include AccessMatchers
+
+ def group
+ @group ||= create(:group)
+ end
+
+ def create_project(access_level)
+ if access_level == :mixed
+ create(:empty_project, :public, group: group)
+ create(:empty_project, :internal, group: group)
+ else
+ create(:empty_project, access_level, group: group)
+ end
+ end
+
+ def group_member(access_level, group = group)
+ level = Object.const_get("Gitlab::Access::#{access_level.upcase}")
+
+ create(:user).tap do |user|
+ group.add_user(user, level)
+ end
+ end
+
+ describe 'GET /groups/new' do
+ subject { new_group_path }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe 'GET /groups/:path' do
+ subject { group_path(group) }
+
+ context 'with public projects' do
+ let!(:project) { create_project(:public) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ context 'with mixed projects' do
+ let!(:project) { create_project(:mixed) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ context 'with internal projects' do
+ let!(:project) { create_project(:internal) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ context 'with no projects' do
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+ end
+
+ describe 'GET /groups/:path/issues' do
+ subject { issues_group_path(group) }
+
+ context 'with public projects' do
+ let!(:project) { create_project(:public) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ context 'with mixed projects' do
+ let!(:project) { create_project(:mixed) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ context 'with internal projects' do
+ let!(:project) { create_project(:internal) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ context 'with no projects' do
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+ end
+
+ describe 'GET /groups/:path/merge_requests' do
+ subject { merge_requests_group_path(group) }
+
+ context 'with public projects' do
+ let!(:project) { create_project(:public) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ context 'with mixed projects' do
+ let!(:project) { create_project(:mixed) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ context 'with internal projects' do
+ let!(:project) { create_project(:internal) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ context 'with no projects' do
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+ end
+
+ describe 'GET /groups/:path/group_members' do
+ subject { group_group_members_path(group) }
+
+ context 'with public projects' do
+ let!(:project) { create_project(:public) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ context 'with mixed projects' do
+ let!(:project) { create_project(:mixed) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ context 'with internal projects' do
+ let!(:project) { create_project(:internal) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ context 'with no projects' do
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_allowed_for group_member(:master) }
+ it { is_expected.to be_allowed_for group_member(:reporter) }
+ it { is_expected.to be_allowed_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+ end
+
+ describe 'GET /groups/:path/edit' do
+ subject { edit_group_path(group) }
+
+ context 'with public projects' do
+ let!(:project) { create_project(:public) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_denied_for group_member(:master) }
+ it { is_expected.to be_denied_for group_member(:reporter) }
+ it { is_expected.to be_denied_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ context 'with mixed projects' do
+ let!(:project) { create_project(:mixed) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_denied_for group_member(:master) }
+ it { is_expected.to be_denied_for group_member(:reporter) }
+ it { is_expected.to be_denied_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ context 'with internal projects' do
+ let!(:project) { create_project(:internal) }
+
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_denied_for group_member(:master) }
+ it { is_expected.to be_denied_for group_member(:reporter) }
+ it { is_expected.to be_denied_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ context 'with no projects' do
+ it { is_expected.to be_allowed_for group_member(:owner) }
+ it { is_expected.to be_denied_for group_member(:master) }
+ it { is_expected.to be_denied_for group_member(:reporter) }
+ it { is_expected.to be_denied_for group_member(:guest) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+ end
+end
diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb
index bcabc2d53ac..c19678ab381 100644
--- a/spec/features/security/profile_access_spec.rb
+++ b/spec/features/security/profile_access_spec.rb
@@ -1,18 +1,11 @@
require 'spec_helper'
describe "Profile access", feature: true do
- before do
- @u1 = create(:user)
- end
-
- describe "GET /login" do
- it { expect(new_user_session_path).not_to be_not_found_for :visitor }
- end
+ include AccessMatchers
describe "GET /profile/keys" do
subject { profile_keys_path }
- it { is_expected.to be_allowed_for @u1 }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :visitor }
@@ -21,7 +14,6 @@ describe "Profile access", feature: true do
describe "GET /profile" do
subject { profile_path }
- it { is_expected.to be_allowed_for @u1 }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :visitor }
@@ -30,7 +22,6 @@ describe "Profile access", feature: true do
describe "GET /profile/account" do
subject { profile_account_path }
- it { is_expected.to be_allowed_for @u1 }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :visitor }
@@ -39,7 +30,6 @@ describe "Profile access", feature: true do
describe "GET /profile/preferences" do
subject { profile_preferences_path }
- it { is_expected.to be_allowed_for @u1 }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :visitor }
@@ -48,7 +38,6 @@ describe "Profile access", feature: true do
describe "GET /profile/audit_log" do
subject { audit_log_profile_path }
- it { is_expected.to be_allowed_for @u1 }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :visitor }
@@ -57,7 +46,6 @@ describe "Profile access", feature: true do
describe "GET /profile/notifications" do
subject { profile_notifications_path }
- it { is_expected.to be_allowed_for @u1 }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :visitor }
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 4649e58cb1a..57563add74c 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe "Internal Project Access", feature: true do
+ include AccessMatchers
+
let(:project) { create(:project, :internal) }
let(:master) { create(:user) }
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index 2866bf0355b..a1e111c6cab 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe "Private Project Access", feature: true do
+ include AccessMatchers
+
let(:project) { create(:project) }
let(:master) { create(:user) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 554c96bcdc5..655d2c8b7d9 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe "Public Project Access", feature: true do
+ include AccessMatchers
+
let(:project) { create(:project) }
let(:master) { create(:user) }
@@ -17,7 +19,6 @@ describe "Public Project Access", feature: true do
# readonly
project.team << [reporter, :reporter]
-
end
describe "Project should be public" do
diff --git a/spec/fixtures/GoogleCodeProjectHosting.json b/spec/fixtures/GoogleCodeProjectHosting.json
index d05e77271ae..67bb3bae5b7 100644
--- a/spec/fixtures/GoogleCodeProjectHosting.json
+++ b/spec/fixtures/GoogleCodeProjectHosting.json
@@ -382,6 +382,11 @@
"fileName" : "screenshot.png",
"fileSize" : 0,
"mimetype" : "image/png"
+ }, {
+ "attachmentId" : "001",
+ "fileName" : "screenshot1.PNG",
+ "fileSize" : 0,
+ "mimetype" : "image/x-png"
} ]
}, {
"id" : 1,
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 02ab46c905a..41d12afa9ce 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -100,6 +100,13 @@ Markdown should be usable inside a link. Let's try!
- [**text**](#link-strong)
- [`text`](#link-code)
+### RelativeLinkFilter
+
+Linking to a file relative to this project's repository should work.
+
+[Relative Link](doc/README.md)
+![Relative Image](app/assets/images/touch-icon-ipad.png)
+
### EmojiFilter
Because life would be :zzz: without Emoji, right? :rocket:
@@ -123,9 +130,9 @@ These are all plain text that should get turned into links:
But it shouldn't autolink text inside certain tags:
-- <code>http://about.gitlab.com/</code>
-- <a>http://about.gitlab.com/</a>
-- <kbd>http://about.gitlab.com/</kbd>
+- <code>http://code.gitlab.com/</code>
+- <a>http://a.gitlab.com/</a>
+- <kbd>http://kbd.gitlab.com/</kbd>
### ExternalLinkFilter
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
new file mode 100644
index 00000000000..e47a54fdac5
--- /dev/null
+++ b/spec/helpers/auth_helper_spec.rb
@@ -0,0 +1,20 @@
+require "spec_helper"
+
+describe AuthHelper do
+ describe "button_based_providers" do
+ it 'returns all enabled providers' do
+ allow(helper).to receive(:auth_providers) { [:twitter, :github] }
+ expect(helper.button_based_providers).to include(*[:twitter, :github])
+ end
+
+ it 'does not return ldap provider' do
+ allow(helper).to receive(:auth_providers) { [:twitter, :ldapmain] }
+ expect(helper.button_based_providers).to include(:twitter)
+ end
+
+ it 'returns empty array' do
+ allow(helper).to receive(:auth_providers) { [] }
+ expect(helper.button_based_providers).to eq([])
+ end
+ end
+end
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index e49e4e6d5d8..76009c36099 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -6,6 +6,14 @@ describe BlobHelper do
let(:no_context_content) { ":type \"assem\"))" }
let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" }
let(:split_content) { blob_content.split("\n") }
+ let(:multiline_content) do
+ %q(
+ def test(input):
+ """This is line 1 of a multi-line comment.
+ This is line 2.
+ """
+ )
+ end
it 'should return plaintext for unknown lexer context' do
result = highlight(blob_name, no_context_content, nowrap: true, continue: false)
@@ -29,5 +37,15 @@ describe BlobHelper do
result = split_content.map{ |content| highlight(blob_name, content, nowrap: true, continue: true) }
expect(result).to eq(expected)
end
+
+ it 'should highlight multi-line comments' do
+ result = highlight(blob_name, multiline_content, nowrap: true, continue: false)
+ html = Nokogiri::HTML(result)
+ lines = html.search('.s')
+ expect(lines.count).to eq(3)
+ expect(lines[0].text).to eq('"""This is line 1 of a multi-line comment.')
+ expect(lines[1].text).to eq(' This is line 2.')
+ expect(lines[2].text).to eq(' """')
+ end
end
end
diff --git a/spec/helpers/oauth_helper_spec.rb b/spec/helpers/oauth_helper_spec.rb
deleted file mode 100644
index 3ef35f35102..00000000000
--- a/spec/helpers/oauth_helper_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require "spec_helper"
-
-describe OauthHelper do
- describe "additional_providers" do
- it 'returns all enabled providers' do
- allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :github] }
- expect(helper.additional_providers).to include(*[:twitter, :github])
- end
-
- it 'does not return ldap provider' do
- allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :ldapmain] }
- expect(helper.additional_providers).to include(:twitter)
- end
-
- it 'returns empty array' do
- allow(helper).to receive(:enabled_oauth_providers) { [] }
- expect(helper.additional_providers).to eq([])
- end
- end
-end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index beb9b4e438e..99abb95d906 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -22,7 +22,7 @@ describe ProjectsHelper do
let(:user) { create(:user) }
- it "returns false if there are no approipriate permissions" do
+ it "returns false if there are no appropriate permissions" do
allow(helper).to receive(:can?) { false }
expect(helper.can_change_visibility_level?(project, user)).to be_falsey
@@ -52,4 +52,22 @@ describe ProjectsHelper do
end
end
end
+
+ describe "readme_cache_key" do
+ let(:project) { create(:project) }
+
+ before do
+ helper.instance_variable_set(:@project, project)
+ end
+
+ it "returns a valid cach key" do
+ expect(helper.send(:readme_cache_key)).to eq("#{project.id}-#{project.commit.id}-readme")
+ end
+
+ it "returns a valid cache key if HEAD does not exist" do
+ allow(project).to receive(:commit) { nil }
+
+ expect(helper.send(:readme_cache_key)).to eq("#{project.id}-nil-readme")
+ end
+ end
end
diff --git a/spec/javascripts/fixtures/line_highlighter.html.haml b/spec/javascripts/fixtures/line_highlighter.html.haml
index 15ad1d8968f..da1ebcdb23c 100644
--- a/spec/javascripts/fixtures/line_highlighter.html.haml
+++ b/spec/javascripts/fixtures/line_highlighter.html.haml
@@ -2,7 +2,9 @@
.file-content
.line-numbers
- 1.upto(25) do |i|
- %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}= i
+ %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
+ %i.fa.fa-link
+ = i
%pre.code.highlight
%code
- 1.upto(25) do |i|
diff --git a/spec/javascripts/line_highlighter_spec.js.coffee b/spec/javascripts/line_highlighter_spec.js.coffee
index 14fa487ff7f..57453c716a5 100644
--- a/spec/javascripts/line_highlighter_spec.js.coffee
+++ b/spec/javascripts/line_highlighter_spec.js.coffee
@@ -48,6 +48,14 @@ describe 'LineHighlighter', ->
clickLine(13)
expect(spy).toHaveBeenPrevented()
+ it 'handles clicking on a child icon element', ->
+ spy = spyOn(@class, 'setHash').and.callThrough()
+
+ $('#L13 i').mousedown().click()
+
+ expect(spy).toHaveBeenCalledWith(13)
+ expect($('#LC13')).toHaveClass(@css)
+
describe 'without shiftKey', ->
it 'highlights one line when clicked', ->
clickLine(13)
diff --git a/spec/javascripts/merge_request_tabs_spec.js.coffee b/spec/javascripts/merge_request_tabs_spec.js.coffee
index 6cc96fb68a0..a0cfba455ea 100644
--- a/spec/javascripts/merge_request_tabs_spec.js.coffee
+++ b/spec/javascripts/merge_request_tabs_spec.js.coffee
@@ -51,6 +51,12 @@ describe 'MergeRequestTabs', ->
expect(@subject('notes')).toBe('/foo/bar/merge_requests/1')
expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits')
+ it 'changes from diffs.html', ->
+ @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/diffs.html')
+
+ expect(@subject('notes')).toBe('/foo/bar/merge_requests/1')
+ expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits')
+
it 'changes from notes', ->
@class._location = stubLocation(pathname: '/foo/bar/merge_requests/1')
diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb
index c53ddeb87b5..f49cbb7f532 100644
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ b/spec/lib/gitlab/google_code_import/importer_spec.rb
@@ -65,6 +65,7 @@ describe Gitlab::GoogleCodeImport::Importer do
expect(issue.description).to include('all the best!')
expect(issue.description).to include('[tint2_task_scrolling.diff](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/tint2_task_scrolling.diff)')
expect(issue.description).to include('![screenshot.png](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/screenshot.png)')
+ expect(issue.description).to include('![screenshot1.PNG](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/screenshot1.PNG)')
end
it "imports issue comments" do
diff --git a/spec/lib/gitlab/markdown/relative_link_filter_spec.rb b/spec/lib/gitlab/markdown/relative_link_filter_spec.rb
index 5ee5310825d..7f4d67e403f 100644
--- a/spec/lib/gitlab/markdown/relative_link_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/relative_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# encoding: UTF-8
+
require 'spec_helper'
module Gitlab::Markdown
@@ -101,6 +103,20 @@ module Gitlab::Markdown
expect(doc.at_css('a')['href']).to eq 'http://example.com'
end
+ it 'supports Unicode filenames' do
+ path = 'files/images/한글.png'
+ escaped = Addressable::URI.escape(path)
+
+ # Stub these methods so the file doesn't actually need to be in the repo
+ allow_any_instance_of(described_class).to receive(:file_exists?).
+ and_return(true)
+ allow_any_instance_of(described_class).
+ to receive(:image?).with(path).and_return(true)
+
+ doc = filter(image(escaped))
+ expect(doc.at_css('img')['src']).to match '/raw/'
+ end
+
context 'when requested path is a file in the repo' do
let(:requested_path) { 'doc/api/README.md' }
include_examples :relative_to_requested
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index d648f4078be..bc14ff98fd8 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -14,11 +14,14 @@
# default_branch_protection :integer default(2)
# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
+# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null
-# session_expire_delay :integer default(10080), not null
# default_project_visibility :integer
# default_snippet_visibility :integer
# restricted_signup_domains :text
+# user_oauth_applications :boolean default(TRUE)
+# after_sign_out_path :string(255)
+# session_expire_delay :integer default(10080), not null
#
require 'spec_helper'
diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb
index fedc37c9b94..a14384c87b4 100644
--- a/spec/models/project_services/gitlab_ci_service_spec.rb
+++ b/spec/models/project_services/gitlab_ci_service_spec.rb
@@ -26,6 +26,33 @@ describe GitlabCiService do
it { is_expected.to have_one(:service_hook) }
end
+ describe 'validations' do
+ context 'active' do
+ before { allow(subject).to receive(:activated?).and_return(true) }
+
+ it { is_expected.to validate_presence_of(:token) }
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
+ it { is_expected.to allow_value('http://ci.example.com/project/1').for(:project_url) }
+ it { is_expected.not_to allow_value('token with spaces').for(:token) }
+ it { is_expected.not_to allow_value('token/with%spaces').for(:token) }
+ it { is_expected.not_to allow_value('this is not url').for(:project_url) }
+ it { is_expected.not_to allow_value('http//noturl').for(:project_url) }
+ it { is_expected.not_to allow_value('ftp://ci.example.com/projects/3').for(:project_url) }
+ end
+
+ context 'inactive' do
+ before { allow(subject).to receive(:activated?).and_return(false) }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
+ it { is_expected.to allow_value('http://ci.example.com/project/1').for(:project_url) }
+ it { is_expected.to allow_value('token with spaces').for(:token) }
+ it { is_expected.to allow_value('ftp://ci.example.com/projects/3').for(:project_url) }
+ end
+ end
+
describe 'commits methods' do
before do
@service = GitlabCiService.new
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 721ae88c120..2fcbd5ae108 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -21,12 +21,13 @@
# import_url :string(255)
# visibility_level :integer default(0), not null
# archived :boolean default(FALSE), not null
+# avatar :string(255)
# import_status :string(255)
# repository_size :float default(0.0)
# star_count :integer default(0), not null
# import_type :string(255)
# import_source :string(255)
-# avatar :string(255)
+# commit_count :integer default(0)
#
require 'spec_helper'
@@ -110,14 +111,20 @@ describe Project do
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
end
- it 'returns the full web URL for this repo' do
- project = Project.new(path: 'somewhere')
- expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/somewhere")
+ describe "#web_url" do
+ let(:project) { create(:empty_project, path: "somewhere") }
+
+ it 'returns the full web URL for this repo' do
+ expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.path}/somewhere")
+ end
end
- it 'returns the web URL without the protocol for this repo' do
- project = Project.new(path: 'somewhere')
- expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/somewhere")
+ describe "#web_url_without_protocol" do
+ let(:project) { create(:empty_project, path: "somewhere") }
+
+ it 'returns the web URL without the protocol for this repo' do
+ expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/#{project.namespace.path}/somewhere")
+ end
end
describe 'last_activity methods' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 16902317f10..922e9ebf844 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -57,6 +57,7 @@
# otp_backup_codes :text
# public_email :string(255) default(""), not null
# dashboard :integer default(0)
+# project_view :integer default(0)
#
require 'spec_helper'
@@ -442,6 +443,18 @@ describe User do
end
end
+ describe '.find_by_username!' do
+ it 'raises RecordNotFound' do
+ expect { described_class.find_by_username!('JohnDoe') }.
+ to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'is case-insensitive' do
+ user = create(:user, username: 'JohnDoe')
+ expect(described_class.find_by_username!('JOHNDOE')).to eq user
+ end
+ end
+
describe 'all_ssh_keys' do
it { is_expected.to have_many(:keys).dependent(:destroy) }
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index cb6e5e89625..5c1b58535cc 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -14,10 +14,13 @@ describe API::API, api: true do
describe "GET /projects/:id/repository/branches" do
it "should return an array of project branches" do
+ project.repository.expire_cache
+
get api("/projects/#{project.id}/repository/branches", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
- expect(json_response.first['name']).to eq(project.repository.branch_names.first)
+ branch_names = json_response.map { |x| x['name'] }
+ expect(branch_names).to match_array(project.repository.branch_names)
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 780ff93a232..942768fa254 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -8,6 +8,7 @@ describe API::API, api: true do
let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test") }
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test") }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
+ let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
before do
project.team << [user, :reporters]
@@ -395,13 +396,14 @@ describe API::API, api: true do
end
describe "GET :id/merge_request/:merge_request_id/comments" do
- it "should return merge_request comments" do
+ it "should return merge_request comments ordered by created_at" do
get api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect(json_response.length).to eq(2)
expect(json_response.first['note']).to eq("a comment on a MR")
expect(json_response.first['author']['id']).to eq(user.id)
+ expect(json_response.last['note']).to eq("another comment on a MR")
end
it "should return a 404 error if merge_request_id not found" do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 4178bb2e836..5bd8206b890 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -89,7 +89,7 @@ describe API::API, api: true do
it 'returns projects in the correct order when ci_enabled_first parameter is passed' do
[project, project2, project3].each{ |project| project.build_missing_services }
- project2.gitlab_ci_service.update(active: true, token: "token", project_url: "url")
+ project2.gitlab_ci_service.update(active: true, token: "token", project_url: "http://ci.example.com/projects/1")
get api('/projects', user), { ci_enabled_first: 'true' }
expect(response.status).to eq(200)
expect(json_response).to be_an Array
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 51c543578df..6d29a28580a 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -7,7 +7,7 @@ describe API::API, api: true do
describe "POST /projects/:id/services/gitlab-ci" do
it "should update gitlab-ci settings" do
- put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secret-token', project_url: "http://ci.example.com/projects/1"
+ put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secrettoken', project_url: "http://ci.example.com/projects/1"
expect(response.status).to eq(200)
end
@@ -17,6 +17,18 @@ describe API::API, api: true do
expect(response.status).to eq(400)
end
+
+ it "should return if the format of token is invalid" do
+ put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "http://ci.example.com/projects/1", active: true
+
+ expect(response.status).to eq(404)
+ end
+
+ it "should return if the format of token is invalid" do
+ put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "ftp://ci.example/projects/1", active: true
+
+ expect(response.status).to eq(404)
+ end
end
describe "DELETE /projects/:id/services/gitlab-ci" do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index c4dd1f76cf2..f2aa369985e 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -6,6 +6,7 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:key) { create(:key, user: user) }
+ let(:email) { create(:email, user: user) }
describe "GET /users" do
context "when unauthenticated" do
@@ -384,6 +385,87 @@ describe API::API, api: true do
end
end
+ describe "POST /users/:id/emails" do
+ before { admin }
+
+ it "should not create invalid email" do
+ post api("/users/#{user.id}/emails", admin), {}
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('400 (Bad request) "email" not given')
+ end
+
+ it "should create email" do
+ email_attrs = attributes_for :email
+ expect do
+ post api("/users/#{user.id}/emails", admin), email_attrs
+ end.to change{ user.emails.count }.by(1)
+ end
+ end
+
+ describe 'GET /user/:uid/emails' do
+ before { admin }
+
+ context 'when unauthenticated' do
+ it 'should return authentication error' do
+ get api("/users/#{user.id}/emails")
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'should return 404 for non-existing user' do
+ get api('/users/999999/emails', admin)
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'should return array of emails' do
+ user.emails << email
+ user.save
+ get api("/users/#{user.id}/emails", admin)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['email']).to eq(email.email)
+ end
+ end
+ end
+
+ describe 'DELETE /user/:uid/emails/:id' do
+ before { admin }
+
+ context 'when unauthenticated' do
+ it 'should return authentication error' do
+ delete api("/users/#{user.id}/emails/42")
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'should delete existing email' do
+ user.emails << email
+ user.save
+ expect do
+ delete api("/users/#{user.id}/emails/#{email.id}", admin)
+ end.to change { user.emails.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should return 404 error if user not found' do
+ user.emails << email
+ user.save
+ delete api("/users/999999/emails/#{email.id}", admin)
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'should return 404 error if email not foud' do
+ delete api("/users/#{user.id}/emails/42", admin)
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Email Not Found')
+ end
+ end
+ end
+
describe "DELETE /users/:id" do
before { admin }
@@ -528,6 +610,95 @@ describe API::API, api: true do
end
end
+ describe "GET /user/emails" do
+ context "when unauthenticated" do
+ it "should return authentication error" do
+ get api("/user/emails")
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context "when authenticated" do
+ it "should return array of emails" do
+ user.emails << email
+ user.save
+ get api("/user/emails", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first["email"]).to eq(email.email)
+ end
+ end
+ end
+
+ describe "GET /user/emails/:id" do
+ it "should return single email" do
+ user.emails << email
+ user.save
+ get api("/user/emails/#{email.id}", user)
+ expect(response.status).to eq(200)
+ expect(json_response["email"]).to eq(email.email)
+ end
+
+ it "should return 404 Not Found within invalid ID" do
+ get api("/user/emails/42", user)
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Not found')
+ end
+
+ it "should return 404 error if admin accesses user's email" do
+ user.emails << email
+ user.save
+ admin
+ get api("/user/emails/#{email.id}", admin)
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Not found')
+ end
+ end
+
+ describe "POST /user/emails" do
+ it "should create email" do
+ email_attrs = attributes_for :email
+ expect do
+ post api("/user/emails", user), email_attrs
+ end.to change{ user.emails.count }.by(1)
+ expect(response.status).to eq(201)
+ end
+
+ it "should return a 401 error if unauthorized" do
+ post api("/user/emails"), email: 'some email'
+ expect(response.status).to eq(401)
+ end
+
+ it "should not create email with invalid email" do
+ post api("/user/emails", user), {}
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('400 (Bad request) "email" not given')
+ end
+ end
+
+ describe "DELETE /user/emails/:id" do
+ it "should delete existed email" do
+ user.emails << email
+ user.save
+ expect do
+ delete api("/user/emails/#{email.id}", user)
+ end.to change{user.emails.count}.by(-1)
+ expect(response.status).to eq(200)
+ end
+
+ it "should return success if email ID not found" do
+ delete api("/user/emails/42", user)
+ expect(response.status).to eq(200)
+ end
+
+ it "should return 401 error if unauthorized" do
+ user.emails << email
+ user.save
+ delete api("/user/emails/#{email.id}")
+ expect(response.status).to eq(401)
+ end
+ end
+
describe 'PUT /user/:id/block' do
before { admin }
it 'should block existing user' do
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 97b206c9854..66cdfd5d758 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -4,13 +4,19 @@ describe Projects::CreateService do
describe :create_by_user do
before do
@user = create :user
- @admin = create :user, admin: true
@opts = {
name: "GitLab",
namespace: @user.namespace
}
end
+ it 'creates services on Project creation' do
+ project = create_project(@user, @opts)
+ project.reload
+
+ expect(project.services).not_to be_empty
+ end
+
context 'user namespace' do
before do
@project = create_project(@user, @opts)
@@ -75,7 +81,9 @@ describe Projects::CreateService do
end
it 'should allow a restricted visibility level for admins' do
- project = create_project(@admin, @opts)
+ admin = create(:admin)
+ project = create_project(admin, @opts)
+
expect(project.errors.any?).to be(false)
expect(project.saved?).to be(true)
end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 439a492cea9..c04e842c67e 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -29,7 +29,7 @@ describe Projects::ForkService do
it "fails due to transaction failure" do
@to_project = fork_project(@from_project, @to_user, false)
expect(@to_project.errors).not_to be_empty
- expect(@to_project.errors[:base]).to include("Failed to fork repository")
+ expect(@to_project.errors[:base]).to include("Failed to fork repository via gitlab-shell")
end
end
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
new file mode 100644
index 00000000000..2a868aed73b
--- /dev/null
+++ b/spec/support/markdown_feature.rb
@@ -0,0 +1,106 @@
+# This is a helper class used by the GitLab Markdown feature spec
+#
+# Because the feature spec only cares about the output of the Markdown, and the
+# test setup and teardown and parsing is fairly expensive, we only want to do it
+# once. Unfortunately RSpec will not let you access `let`s in a `before(:all)`
+# block, so we fake it by encapsulating all the shared setup in this class.
+#
+# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for
+# reference to the factory-created objects.
+class MarkdownFeature
+ include FactoryGirl::Syntax::Methods
+
+ def user
+ @user ||= create(:user)
+ end
+
+ def group
+ unless @group
+ @group = create(:group)
+ @group.add_user(user, Gitlab::Access::DEVELOPER)
+ end
+
+ @group
+ end
+
+ # Direct references ----------------------------------------------------------
+
+ def project
+ @project ||= create(:project)
+ end
+
+ def issue
+ @issue ||= create(:issue, project: project)
+ end
+
+ def merge_request
+ @merge_request ||= create(:merge_request, :simple, source_project: project)
+ end
+
+ def snippet
+ @snippet ||= create(:project_snippet, project: project)
+ end
+
+ def commit
+ @commit ||= project.commit
+ end
+
+ def commit_range
+ unless @commit_range
+ commit2 = project.commit('HEAD~3')
+ @commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
+ end
+
+ @commit_range
+ end
+
+ def simple_label
+ @simple_label ||= create(:label, name: 'gfm', project: project)
+ end
+
+ def label
+ @label ||= create(:label, name: 'awaiting feedback', project: project)
+ end
+
+ # Cross-references -----------------------------------------------------------
+
+ def xproject
+ unless @xproject
+ namespace = create(:namespace, name: 'cross-reference')
+ @xproject = create(:project, namespace: namespace)
+ @xproject.team << [user, :developer]
+ end
+
+ @xproject
+ end
+
+ def xissue
+ @xissue ||= create(:issue, project: xproject)
+ end
+
+ def xmerge_request
+ @xmerge_request ||= create(:merge_request, :simple, source_project: xproject)
+ end
+
+ def xsnippet
+ @xsnippet ||= create(:project_snippet, project: xproject)
+ end
+
+ def xcommit
+ @xcommit ||= xproject.commit
+ end
+
+ def xcommit_range
+ unless @xcommit_range
+ xcommit2 = xproject.commit('HEAD~2')
+ @xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
+ end
+
+ @xcommit_range
+ end
+
+ def raw_markdown
+ fixture = Rails.root.join('spec/fixtures/markdown.md.erb')
+ ERB.new(File.read(fixture)).result(binding)
+ end
+end
diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb
deleted file mode 100644
index a2f853e3e70..00000000000
--- a/spec/support/matchers.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-RSpec::Matchers.define :be_valid_commit do
- match do |actual|
- actual &&
- actual.id == ValidCommit::ID &&
- actual.message == ValidCommit::MESSAGE &&
- actual.author_name == ValidCommit::AUTHOR_FULL_NAME
- end
-end
-
-def emulate_user(user)
- user = case user
- when :user then create(:user)
- when :visitor then nil
- when :admin then create(:admin)
- else user
- end
- login_with(user) if user
-end
-
-RSpec::Matchers.define :be_allowed_for do |user|
- match do |url|
- emulate_user(user)
- visit url
- status_code != 404 && current_path != new_user_session_path
- end
-end
-
-RSpec::Matchers.define :be_denied_for do |user|
- match do |url|
- emulate_user(user)
- visit url
- status_code == 404 || current_path == new_user_session_path
- end
-end
-
-RSpec::Matchers.define :be_not_found_for do |user|
- match do |url|
- emulate_user(user)
- visit url
- status_code == 404
- end
-end
-
-RSpec::Matchers.define :include_module do |expected|
- match do
- described_class.included_modules.include?(expected)
- end
-
- description do
- "includes the #{expected} module"
- end
-
- failure_message do
- "expected #{described_class} to include the #{expected} module"
- end
-end
-
-# Extend shoulda-matchers
-module Shoulda::Matchers::ActiveModel
- class ValidateLengthOfMatcher
- # Shortcut for is_at_least and is_at_most
- def is_within(range)
- is_at_least(range.min) && is_at_most(range.max)
- end
- end
-end
diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb
new file mode 100644
index 00000000000..558e8b1612f
--- /dev/null
+++ b/spec/support/matchers/access_matchers.rb
@@ -0,0 +1,54 @@
+# AccessMatchers
+#
+# The custom matchers contained in this module are used to test a user's access
+# to a URL by emulating a specific user or type of user account, visiting the
+# URL, and then checking the response status code and resulting path.
+module AccessMatchers
+ extend RSpec::Matchers::DSL
+ include Warden::Test::Helpers
+
+ def emulate_user(user)
+ case user
+ when :user
+ login_as(create(:user))
+ when :visitor
+ logout
+ when :admin
+ login_as(create(:admin))
+ when User
+ login_as(user)
+ else
+ raise ArgumentError, "cannot emulate user #{user}"
+ end
+ end
+
+ def description_for(user, type)
+ if user.kind_of?(User)
+ # User#inspect displays too much information for RSpec's description
+ # messages
+ "be #{type} for supplied User"
+ else
+ "be #{type} for #{user}"
+ end
+ end
+
+ matcher :be_allowed_for do |user|
+ match do |url|
+ emulate_user(user)
+ visit url
+ status_code != 404 && current_path != new_user_session_path
+ end
+
+ description { description_for(user, 'allowed') }
+ end
+
+ matcher :be_denied_for do |user|
+ match do |url|
+ emulate_user(user)
+ visit url
+ status_code == 404 || current_path == new_user_session_path
+ end
+
+ description { description_for(user, 'denied') }
+ end
+end
diff --git a/spec/support/matchers/include_module.rb b/spec/support/matchers/include_module.rb
new file mode 100644
index 00000000000..0a78af1e90e
--- /dev/null
+++ b/spec/support/matchers/include_module.rb
@@ -0,0 +1,13 @@
+RSpec::Matchers.define :include_module do |expected|
+ match do
+ described_class.included_modules.include?(expected)
+ end
+
+ description do
+ "includes the #{expected} module"
+ end
+
+ failure_message do
+ "expected #{described_class} to include the #{expected} module"
+ end
+end
diff --git a/spec/support/matchers/is_within.rb b/spec/support/matchers/is_within.rb
new file mode 100644
index 00000000000..0c35fc7e899
--- /dev/null
+++ b/spec/support/matchers/is_within.rb
@@ -0,0 +1,9 @@
+# Extend shoulda-matchers
+module Shoulda::Matchers::ActiveModel
+ class ValidateLengthOfMatcher
+ # Shortcut for is_at_least and is_at_most
+ def is_within(range)
+ is_at_least(range.min) && is_at_most(range.max)
+ end
+ end
+end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
new file mode 100644
index 00000000000..9df226c3af8
--- /dev/null
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -0,0 +1,156 @@
+# MarkdownMatchers
+#
+# Custom matchers for our custom HTML::Pipeline filters. These are used to test
+# that specific filters are or are not used by our defined pipelines.
+#
+# Must be included manually.
+module MarkdownMatchers
+ extend RSpec::Matchers::DSL
+ include Capybara::Node::Matchers
+
+ # RelativeLinkFilter
+ matcher :parse_relative_links do
+ set_default_markdown_messages
+
+ match do |actual|
+ link = actual.at_css('a:contains("Relative Link")')
+ image = actual.at_css('img[alt="Relative Image"]')
+
+ expect(link['href']).to end_with('master/doc/README.md')
+ expect(image['src']).to end_with('master/app/assets/images/touch-icon-ipad.png')
+ end
+ end
+
+ # EmojiFilter
+ matcher :parse_emoji do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('img.emoji', count: 10)
+ end
+ end
+
+ # TableOfContentsFilter
+ matcher :create_header_links do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('h1 a#gitlab-markdown')
+ expect(actual).to have_selector('h2 a#markdown')
+ expect(actual).to have_selector('h3 a#autolinkfilter')
+ end
+ end
+
+ # AutolinkFilter
+ matcher :create_autolinks do
+ def have_autolink(link)
+ have_link(link, href: link)
+ end
+
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_autolink('http://about.gitlab.com/')
+ expect(actual).to have_autolink('https://google.com/')
+ expect(actual).to have_autolink('ftp://ftp.us.debian.org/debian/')
+ expect(actual).to have_autolink('smb://foo/bar/baz')
+ expect(actual).to have_autolink('irc://irc.freenode.net/git')
+ expect(actual).to have_autolink('http://localhost:3000')
+
+ %w(code a kbd).each do |elem|
+ expect(body).not_to have_selector("#{elem} a")
+ end
+ end
+ end
+
+ # UserReferenceFilter
+ matcher :reference_users do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3)
+ end
+ end
+
+ # IssueReferenceFilter
+ matcher :reference_issues do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('a.gfm.gfm-issue', count: 3)
+ end
+ end
+
+ # MergeRequestReferenceFilter
+ matcher :reference_merge_requests do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3)
+ expect(actual).to have_selector('em a.gfm-merge_request')
+ end
+ end
+
+ # SnippetReferenceFilter
+ matcher :reference_snippets do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2)
+ end
+ end
+
+ # CommitRangeReferenceFilter
+ matcher :reference_commit_ranges do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2)
+ end
+ end
+
+ # CommitReferenceFilter
+ matcher :reference_commits do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('a.gfm.gfm-commit', count: 2)
+ end
+ end
+
+ # LabelReferenceFilter
+ matcher :reference_labels do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('a.gfm.gfm-label', count: 3)
+ end
+ end
+
+ # TaskListFilter
+ matcher :parse_task_lists do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('ul.task-list', count: 2)
+ expect(actual).to have_selector('li.task-list-item', count: 7)
+ expect(actual).to have_selector('input[checked]', count: 3)
+ end
+ end
+end
+
+# Monkeypatch the matcher DSL so that we can reduce some noisy duplication for
+# setting the failure messages for these matchers
+module RSpec::Matchers::DSL::Macros
+ def set_default_markdown_messages
+ failure_message do
+ # expected to parse emoji, but didn't
+ "expected to #{description}, but didn't"
+ end
+
+ failure_message_when_negated do
+ # expected not to parse task lists, but did
+ "expected not to #{description}, but did"
+ end
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 8bdd6b43cdd..8dc687c3580 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -5,18 +5,24 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
+ 'empty-branch' => '7efb185',
'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f',
'fix' => '12d65c8',
'improve/awesome' => '5937ac0',
'markdown' => '0ed8c6c',
- 'master' => '5937ac0'
+ 'master' => '5937ac0',
+ "'test'" => 'e56497b',
}
- FORKED_BRANCH_SHA = BRANCH_SHA.merge({
- 'add-submodule-version-bump' => '3f547c08'
- })
+ # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
+ # need to keep all the branches in sync.
+ # We currently only need a subset of the branches
+ FORKED_BRANCH_SHA = {
+ 'add-submodule-version-bump' => '3f547c08',
+ 'master' => '5937ac0'
+ }
# Test environment
#
@@ -29,6 +35,7 @@ module TestEnv
clean_test_path
FileUtils.mkdir_p(repos_path)
+ FileUtils.mkdir_p(backup_path)
# Setup GitLab shell for test instance
setup_gitlab_shell
@@ -121,6 +128,10 @@ module TestEnv
Gitlab.config.gitlab_shell.repos_path
end
+ def backup_path
+ Gitlab.config.backup.path
+ end
+
def copy_forked_repo_with_submodules(project)
base_repo_path = File.expand_path(forked_repo_path_bare)
target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git")
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index cdcfeba8d1f..23f322e0a62 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -15,6 +15,12 @@ describe 'gitlab:app namespace rake task' do
Rake.application.invoke_task task_name
end
+ def reenable_backup_sub_tasks
+ %w{db repo uploads}.each do |subtask|
+ Rake::Task["gitlab:backup:#{subtask}:create"].reenable
+ end
+ end
+
describe 'backup_restore' do
before do
# avoid writing task output to spec progress
@@ -60,26 +66,47 @@ describe 'gitlab:app namespace rake task' do
Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar'))
end
- before :all do
- # Record the existing backup tars so we don't touch them
- existing_tars = tars_glob
+ def create_backup
+ FileUtils.rm tars_glob
# Redirect STDOUT and run the rake task
orig_stdout = $stdout
$stdout = StringIO.new
+ reenable_backup_sub_tasks
run_rake_task('gitlab:backup:create')
+ reenable_backup_sub_tasks
$stdout = orig_stdout
- @backup_tar = (tars_glob - existing_tars).first
+ @backup_tar = tars_glob.first
end
- after :all do
+ before do
+ create_backup
+ end
+
+ after do
FileUtils.rm(@backup_tar)
end
- it 'should set correct permissions on the tar file' do
- expect(File.exist?(@backup_tar)).to be_truthy
- expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100600')
+ context 'archive file permissions' do
+ it 'should set correct permissions on the tar file' do
+ expect(File.exist?(@backup_tar)).to be_truthy
+ expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100600')
+ end
+
+ context 'with custom archive_permissions' do
+ before do
+ allow(Gitlab.config.backup).to receive(:archive_permissions).and_return(0651)
+ # We created a backup in a before(:all) so it got the default permissions.
+ # We now need to do some work to create a _new_ backup file using our stub.
+ FileUtils.rm(@backup_tar)
+ create_backup
+ end
+
+ it 'uses the custom permissions' do
+ expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100651')
+ end
+ end
end
it 'should set correct permissions on the tar contents' do
@@ -110,12 +137,9 @@ describe 'gitlab:app namespace rake task' do
before :all do
@origin_cd = Dir.pwd
- Rake::Task["gitlab:backup:db:create"].reenable
- Rake::Task["gitlab:backup:repo:create"].reenable
- Rake::Task["gitlab:backup:uploads:create"].reenable
+ reenable_backup_sub_tasks
- # Record the existing backup tars so we don't touch them
- existing_tars = tars_glob
+ FileUtils.rm tars_glob
# Redirect STDOUT and run the rake task
orig_stdout = $stdout
@@ -124,7 +148,7 @@ describe 'gitlab:app namespace rake task' do
run_rake_task('gitlab:backup:create')
$stdout = orig_stdout
- @backup_tar = (tars_glob - existing_tars).first
+ @backup_tar = tars_glob.first
end
after :all do