diff --git a/CHANGELOG b/CHANGELOG index aa9b820de95c9e79f79b4613199c9e7a1da53b6a..a41c07da58f75d4bee365886ea809d45a9bf61eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,18 @@ +v 7.5.0 + - API: Add support for Hipchat (Kevin Houdebert) + - Add time zone configuration on gitlab.yml (Sullivan Senechal) + - Fix LDAP authentication for Git HTTP access + - Fix LDAP config lookup for provider 'ldap' + - Drop all sequences during Postgres database restore + - Project title links to project homepage (Ben Bodenmiller) + - Add Atlassian Bamboo CI service (Drew Blessing) + - Mentioned @user will receive email even if he is not participating in issue or commit + - Session API: Use case-insensitive authentication like in UI (Andrey Krivko) + - Tie up loose ends with annotated tags: API & UI (Sean Edge) + - Return valid json for deleting branch via API (sponsored by O'Reilly Media) + - Expose username in project events API (sponsored by O'Reilly Media) + - Adds comments to commits in the API + v 7.4.3 - Fix raw snippets view - Fix security issue for member api @@ -46,7 +61,7 @@ v 7.4.0 - Fix ambiguous sha problem with mentioned commit - Fixed bug with apostrophe when at mentioning users - Add active directory ldap option - - Developers can push to wiki repo. Protected branches does not affect wiki repo any more + - Developers can push to wiki repo. Protected branches does not affect wiki repo any more - Faster rev list - Fix branch removal diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce454a11a08c810c2493a0aafaff74abd8cd0dd3..71435bc600d820df649fbb076311c35b7e2f1bfb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,6 +54,8 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls). +If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint. + ### Merge request guidelines If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows: @@ -101,7 +103,11 @@ For examples of feedback on merge requests please look at already [closed merge 1. Contains functionality we think other users will benefit from too 1. Doesn't add configuration options since they complicate future changes 1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging. -1. It conforms to the following style guides +1. It conforms to the following style guides. + If your change touches a line that does not follow the style, + modify the entire line to follow it. This prevents linting tools from generating warnings. + Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications + may leave style non-compliant. ## Style guides diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 7ec1d6db40877765247db18e7f9a4e36a0def4ad..ccbccc3dc62631f22ff358ac418e52401ec770b4 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.1.0 +2.2.0 diff --git a/Gemfile b/Gemfile index 1e89a7f26dc501d80c97ddf537f0357b83c9c108..2c91d84b9dd4cc3b0344f685f67c12f5aaaa6179 100644 --- a/Gemfile +++ b/Gemfile @@ -37,7 +37,7 @@ gem "gitlab_git", '7.0.0.rc10' gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' # LDAP Auth -gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap" +gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap" gem 'net-ldap' # Git Wiki @@ -145,7 +145,7 @@ gem "gitlab-flowdock-git-hook", "~> 0.4.2" gem "gemnasium-gitlab-service", "~> 0.2" # Slack integration -gem "slack-notifier", "~> 0.3.2" +gem "slack-notifier", "~> 1.0.0" # d3 gem "d3_rails", "~> 3.1.4" diff --git a/Gemfile.lock b/Gemfile.lock index cfa371ecde108a13fbd577e2e9ead168df1d56ef..f3ff6e79523fa398e291235cbbf91f815b301736 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -185,11 +185,11 @@ GEM gitlab-linguist (~> 3.0) rugged (~> 0.21.0) gitlab_meta (7.0) - gitlab_omniauth-ldap (1.1.0) - net-ldap (~> 0.7.0) + gitlab_omniauth-ldap (1.2.0) + net-ldap (~> 0.9) omniauth (~> 1.0) pyu-ruby-sasl (~> 0.0.3.1) - rubyntlm (~> 0.1.1) + rubyntlm (~> 0.3) gollum-lib (3.0.0) github-markup (~> 1.1.0) gitlab-grit (~> 2.6.5) @@ -300,7 +300,7 @@ GEM multi_xml (0.5.5) multipart-post (1.2.0) mysql2 (0.3.16) - net-ldap (0.7.0) + net-ldap (0.9.0) net-scp (1.1.2) net-ssh (>= 2.6.5) net-ssh (2.8.0) @@ -446,7 +446,7 @@ GEM rspec-expectations (~> 2.14.0) rspec-mocks (~> 2.14.0) ruby-progressbar (1.2.0) - rubyntlm (0.1.1) + rubyntlm (0.4.0) rubypants (0.2.0) rugged (0.21.0) safe_yaml (0.9.7) @@ -493,7 +493,7 @@ GEM rack-protection (~> 1.4) tilt (~> 1.3, >= 1.3.4) six (0.2.0) - slack-notifier (0.3.2) + slack-notifier (1.0.0) slim (2.0.2) temple (~> 0.6.6) tilt (>= 1.3.3, < 2.1) @@ -632,7 +632,7 @@ DEPENDENCIES gitlab_emoji (~> 0.0.1.1) gitlab_git (= 7.0.0.rc10) gitlab_meta (= 7.0) - gitlab_omniauth-ldap (= 1.1.0) + gitlab_omniauth-ldap (= 1.2.0) gollum-lib (~> 3.0.0) gon (~> 5.0.0) grape (~> 0.6.1) @@ -696,7 +696,7 @@ DEPENDENCIES simplecov sinatra six - slack-notifier (~> 0.3.2) + slack-notifier (~> 1.0.0) slim spinach-rails spring (= 1.1.3) diff --git a/README.md b/README.md index 14f2181a62ebf28efbb85f28e1fd9fe44c0be20b..4d4c52fec94589beeeb6f72449350ef3e087161e 100644 --- a/README.md +++ b/README.md @@ -57,14 +57,8 @@ Since a manual installation is a lot of work and error prone we strongly recomme ## Third-party applications -Access GitLab from multiple platforms with applications below. -These applications are maintained by contributors, GitLab B.V. does not offer support for them. - -- [iPhone app](http://gitlabcontrol.com/) -- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en) -- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi) -- [Command line client](https://github.com/drewblessing/gitlab-cli) -- [Ruby API wrapper](https://github.com/NARKOZ/gitlab) +There are a lot of applications and API wrappers for GitLab. +Find them [on our website](https://about.gitlab.com/applications/). ### New versions diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee index fdefbfb92bd9f148c3cd0611672357a79994e370..4f76d8ce48603ab849a4147a47e139d2a2238a22 100644 --- a/app/assets/javascripts/activities.js.coffee +++ b/app/assets/javascripts/activities.js.coffee @@ -1,4 +1,4 @@ -class Activities +class @Activities constructor: -> Pager.init 20, true $(".event_filter_link").bind "click", (event) => @@ -27,5 +27,3 @@ class Activities event_filters.splice index, 1 $.cookie "event_filter", event_filters.join(","), { path: '/' } - -@Activities = Activities diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index a333eed87f24e465ba7e56fb9575207eba6b23d5..bcb2e6df7c01450cc9bccd1a530f8811d2ae6976 100644 --- a/app/assets/javascripts/admin.js.coffee +++ b/app/assets/javascripts/admin.js.coffee @@ -1,4 +1,4 @@ -class Admin +class @Admin constructor: -> $('input#user_force_random_password').on 'change', (elem) -> elems = $('#user_password, #user_password_confirmation') @@ -51,5 +51,3 @@ class Admin $('li.group_member').bind 'ajax:success', -> Turbolinks.visit(location.href) - -@Admin = Admin diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index ff0d0bb32b957c29046664951fadf1fc7f36a22c..e9a28c1215933da2a27689ea056e9b4c3fd41559 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -18,6 +18,7 @@ #= require jquery.turbolinks #= require turbolinks #= require bootstrap +#= require password_strength #= require select2 #= require raphael #= require g.raphael-min @@ -63,7 +64,7 @@ window.extractLast = (term) -> return split( term ).pop() window.rstrip = (val) -> - return val.replace(/\s+$/, '') + return if val then val.replace(/\s+$/, '') else val # Disable button if text field is empty window.disableButtonIfEmptyField = (field_selector, button_selector) -> diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee index 9db919e5a62c9f840f24abf84958eb1870b88b69..a5f15f80c5c66bfe41f8791a395dfd58826fb43a 100644 --- a/app/assets/javascripts/blob.js.coffee +++ b/app/assets/javascripts/blob.js.coffee @@ -1,4 +1,4 @@ -class BlobView +class @BlobView constructor: -> # handle multi-line select handleMultiSelect = (e) -> @@ -71,6 +71,3 @@ class BlobView # Highlight the correct lines when the hash part of the URL changes $(window).on("hashchange", highlightBlobLines) - - -@BlobView = BlobView diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee index 5f53439ca4b009096571d3c8bc3d09d30e7431b3..0566e239191e2a57ff6628c69ee3d6f5d8d55b16 100644 --- a/app/assets/javascripts/commit.js.coffee +++ b/app/assets/javascripts/commit.js.coffee @@ -1,6 +1,4 @@ -class Commit +class @Commit constructor: -> $('.files .diff-file').each -> new CommitFile(this) - -@Commit = Commit diff --git a/app/assets/javascripts/commit/file.js.coffee b/app/assets/javascripts/commit/file.js.coffee index 4db9116a9de732eee56f3a77286b133084d8e757..83e793863b60623e06d5866d94ca54f2b958608a 100644 --- a/app/assets/javascripts/commit/file.js.coffee +++ b/app/assets/javascripts/commit/file.js.coffee @@ -1,7 +1,5 @@ -class CommitFile +class @CommitFile constructor: (file) -> if $('.image', file).length new ImageFile(file) - -@CommitFile = CommitFile diff --git a/app/assets/javascripts/commit/image-file.js.coffee b/app/assets/javascripts/commit/image-file.js.coffee index 607b85eb45c292eeda2bdc99db81c68db7450572..9e5f49b1f699a337ff0f911532aafb8dc10f3239 100644 --- a/app/assets/javascripts/commit/image-file.js.coffee +++ b/app/assets/javascripts/commit/image-file.js.coffee @@ -1,4 +1,4 @@ -class ImageFile +class @ImageFile # Width where images must fits in, for 2-up this gets divided by 2 @availWidth = 900 @@ -124,5 +124,3 @@ class ImageFile else img.on 'load', => callback.call(this, domImg.naturalWidth, domImg.naturalHeight) - -@ImageFile = ImageFile diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee index 784d7d20bb13d5c04e64d10abf5a82f893aa3aa5..c183e78e513ed62a5f98ac13cfc41a15db308c41 100644 --- a/app/assets/javascripts/commits.js.coffee +++ b/app/assets/javascripts/commits.js.coffee @@ -1,4 +1,4 @@ -class CommitsList +class @CommitsList @data = ref: null limit: 0 @@ -53,5 +53,3 @@ class CommitsList @disable callback: => this.getOld() - -this.CommitsList = CommitsList diff --git a/app/assets/javascripts/confirm_danger_modal.js.coffee b/app/assets/javascripts/confirm_danger_modal.js.coffee index 1687b7d961ceeec2a38851c84ecdefaeb11c2070..bb99edbd09eb5e21fba32774401da7d739902a01 100644 --- a/app/assets/javascripts/confirm_danger_modal.js.coffee +++ b/app/assets/javascripts/confirm_danger_modal.js.coffee @@ -1,4 +1,4 @@ -class ConfirmDangerModal +class @ConfirmDangerModal constructor: (form, text) -> @form = form $('.js-confirm-text').text(text || '') @@ -16,5 +16,3 @@ class ConfirmDangerModal $('.js-confirm-danger-submit').on 'click', => @form.submit() - -@ConfirmDangerModal = ConfirmDangerModal diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee index c4a0ccd9c2ad6c45896d3a4f5bf714ead56a475f..6ef5a539b8fede86d194cf85ee8564a1242bc3ae 100644 --- a/app/assets/javascripts/dashboard.js.coffee +++ b/app/assets/javascripts/dashboard.js.coffee @@ -1,4 +1,4 @@ -class Dashboard +class @Dashboard constructor: -> @initSidebarTab() @@ -28,6 +28,3 @@ class Dashboard # show tab from cookie sidebar_filter = $.cookie(key) $("#" + sidebar_filter).tab('show') if sidebar_filter - - -@Dashboard = Dashboard diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee index dbe00c487dc4bdf69a05c6a0fee2baf7c3863f8c..52b4208524ff80afabcc076fab555094418d1a31 100644 --- a/app/assets/javascripts/diff.js.coffee +++ b/app/assets/javascripts/diff.js.coffee @@ -1,4 +1,4 @@ -class Diff +class @Diff UNFOLD_COUNT = 20 constructor: -> $(document).on('click', '.js-unfold', (event) => @@ -41,6 +41,3 @@ class Diff lines = line.children().slice(0, 2) line_numbers = ($(l).attr('data-linenumber') for l in lines) (parseInt(line_number) for line_number in line_numbers) - - -@Diff = Diff diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 00b52758fa8888726a593828d69ab8fe75d0eeac..fb1adbc4b3d03a424de86d5ccd2feed9c393c0f5 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -58,15 +58,11 @@ class Dispatcher when 'groups:show', 'projects:show' new Activities() shortcut_handler = new ShortcutsNavigation() - when 'projects:new' - new Project() - when 'projects:edit' - new Project() - shortcut_handler = new ShortcutsNavigation() - when 'projects:teams:members:index' - new TeamMembers() when 'groups:members' new GroupMembers() + new UsersSelect() + when 'groups:new', 'groups:edit', 'admin:groups:edit' + new GroupAvatar() when 'projects:tree:show' new TreeView() shortcut_handler = new ShortcutsNavigation() @@ -79,13 +75,33 @@ class Dispatcher # Ensure we don't create a particular shortcut handler here. This is # already created, where the network graph is created. shortcut_handler = true + when 'users:show' + new User() switch path.first() - when 'admin' then new Admin() + when 'admin' + new Admin() + switch path[1] + when 'groups' + new UsersSelect() + when 'projects' + new NamespaceSelect() when 'dashboard' shortcut_handler = new ShortcutsDashboardNavigation() + when 'profiles' + new Profile() when 'projects' + new Project() switch path[1] + when 'edit' + shortcut_handler = new ShortcutsNavigation() + new ProjectNew() + when 'new' + new ProjectNew() + when 'show' + new ProjectShow() + when 'issues', 'merge_requests' + new ProjectUsersSelect() when 'wikis' new Wikis() shortcut_handler = new ShortcutsNavigation() @@ -94,6 +110,7 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' shortcut_handler = new ShortcutsNavigation() + new UsersSelect() # If we haven't installed a custom shortcut handler, install the default one diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee index cf1a37eae3e4c9e7a7f704df36d991e70d2f090c..b39ab0c447580cbd7f5ef80f38f395da8f1bba1b 100644 --- a/app/assets/javascripts/flash.js.coffee +++ b/app/assets/javascripts/flash.js.coffee @@ -1,4 +1,4 @@ -class Flash +class @Flash constructor: (message, type)-> flash = $(".flash-container") flash.html("") @@ -10,5 +10,3 @@ class Flash flash.click -> $(@).fadeOut() flash.show() - -@Flash = Flash diff --git a/app/assets/javascripts/group_avatar.js.coffee b/app/assets/javascripts/group_avatar.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..0825fd3ce52531a6873bf299e32428a16280e43a --- /dev/null +++ b/app/assets/javascripts/group_avatar.js.coffee @@ -0,0 +1,9 @@ +class @GroupAvatar + constructor: -> + $('.js-choose-group-avatar-button').bind "click", -> + form = $(this).closest("form") + form.find(".js-group-avatar-input").click() + $('.js-group-avatar-input').bind "change", -> + form = $(this).closest("form") + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find(".js-avatar-filename").text(filename) diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee index 4b1000f9a6a3e037e771285f5db632d6b869aa28..cc905e91ea2570de625c61fd243177bbc7d3b1b5 100644 --- a/app/assets/javascripts/groups.js.coffee +++ b/app/assets/javascripts/groups.js.coffee @@ -1,17 +1,4 @@ -class GroupMembers +class @GroupMembers constructor: -> $('li.group_member').bind 'ajax:success', -> $(this).fadeOut() - -@GroupMembers = GroupMembers - -$ -> - # avatar - $('.js-choose-group-avatar-button').bind "click", -> - form = $(this).closest("form") - form.find(".js-group-avatar-input").click() - - $('.js-group-avatar-input').bind "change", -> - form = $(this).closest("form") - filename = $(this).val().replace(/^.*[\\\/]/, '') - form.find(".js-avatar-filename").text(filename) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 0e2a2fa792affcca1530bf116d0d63cfc66c9578..597b4695a6daff1678d568747bc82b70a5dd9b82 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -1,4 +1,4 @@ -class Issue +class @Issue constructor: -> $('.edit-issue.inline-update input[type="submit"]').hide() $(".issue-box .inline-update").on "change", "select", -> @@ -15,5 +15,3 @@ class Issue "issue" updateTaskState ) - -@Issue = Issue diff --git a/app/assets/javascripts/labels.js.coffee b/app/assets/javascripts/labels.js.coffee index d306ad64f5bccc86f5289a77d74a351ea61824f1..1bc8840f9ac887233a9e5cf9ec8e394a5c6fdbf7 100644 --- a/app/assets/javascripts/labels.js.coffee +++ b/app/assets/javascripts/labels.js.coffee @@ -1,4 +1,4 @@ -class Labels +class @Labels constructor: -> form = $('.label-form') @setupLabelForm(form) @@ -31,5 +31,3 @@ class Labels # Notify the form, that color has changed $('.label-form').trigger('keyup') e.preventDefault() - -@Labels = Labels diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 9f99ff403f8eb70662eeadf0bc9bc6a976bbc572..46e06424e5a2f83b7e0385a09e416d270d512fae 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -1,4 +1,4 @@ -class MergeRequest +class @MergeRequest constructor: (@opts) -> @initContextWidget() this.$el = $('.merge-request') @@ -132,5 +132,3 @@ class MergeRequest this.$('.automerge_widget').hide() this.$('.merge-in-progress').hide() this.$('.automerge_widget.already_cannot_be_merged').show() - -this.MergeRequest = MergeRequest diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee index ea01c318d4f911ee81ecc0352977c3cffcc833a6..c42f31933d37b5e8f754307bdbd95e3557ca3afd 100644 --- a/app/assets/javascripts/milestone.js.coffee +++ b/app/assets/javascripts/milestone.js.coffee @@ -1,4 +1,4 @@ -class Milestone +class @Milestone @updateIssue: (li, issue_url, data) -> $.ajax type: "PUT" @@ -115,5 +115,3 @@ class Milestone Milestone.updateMergeRequest(ui.item, merge_request_url, data) ).disableSelection() - -@Milestone = Milestone diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee index 00d135d1449d860aaa6fbefa88bc8dded9823f3d..a02c4515ccc1ea1b386f2fee5ad8ebf86f9fce24 100644 --- a/app/assets/javascripts/namespace_select.js.coffee +++ b/app/assets/javascripts/namespace_select.js.coffee @@ -1,24 +1,25 @@ -$ -> - namespaceFormatResult = (namespace) -> - markup = "<div class='namespace-result'>" - markup += "<span class='namespace-kind'>" + namespace.kind + "</span>" - markup += "<span class='namespace-path'>" + namespace.path + "</span>" - markup += "</div>" - markup +class @NamespaceSelect + constructor: -> + namespaceFormatResult = (namespace) -> + markup = "<div class='namespace-result'>" + markup += "<span class='namespace-kind'>" + namespace.kind + "</span>" + markup += "<span class='namespace-path'>" + namespace.path + "</span>" + markup += "</div>" + markup - formatSelection = (namespace) -> - namespace.kind + ": " + namespace.path + formatSelection = (namespace) -> + namespace.kind + ": " + namespace.path - $('.ajax-namespace-select').each (i, select) -> - $(select).select2 - placeholder: "Search for namespace" - multiple: $(select).hasClass('multiselect') - minimumInputLength: 0 - query: (query) -> - Api.namespaces query.term, (namespaces) -> - data = { results: namespaces } - query.callback(data) + $('.ajax-namespace-select').each (i, select) -> + $(select).select2 + placeholder: "Search for namespace" + multiple: $(select).hasClass('multiselect') + minimumInputLength: 0 + query: (query) -> + Api.namespaces query.term, (namespaces) -> + data = { results: namespaces } + query.callback(data) - dropdownCssClass: "ajax-namespace-dropdown" - formatResult: namespaceFormatResult - formatSelection: formatSelection + dropdownCssClass: "ajax-namespace-dropdown" + formatResult: namespaceFormatResult + formatSelection: formatSelection diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index ba8d7a9a2f5189cfd25292ec7ea4f04f0fdb3007..978f83dd442c2d2570794eb2480525541b560505 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -1,4 +1,4 @@ -class Notes +class @Notes @interval: null constructor: (notes_url, note_ids, last_fetched_at) -> @@ -514,7 +514,3 @@ class Notes else form.find('.js-note-target-reopen').text('Reopen') form.find('.js-note-target-close').text('Close') - - - -@Notes = Notes diff --git a/app/assets/javascripts/notes_votes.js.coffee b/app/assets/javascripts/notes_votes.js.coffee index b31eb9ac9de7a014a719814003a696f49e042023..65c149b7886e490717b0f3d333d6ec448b90f487 100644 --- a/app/assets/javascripts/notes_votes.js.coffee +++ b/app/assets/javascripts/notes_votes.js.coffee @@ -1,4 +1,4 @@ -class NotesVotes +class @NotesVotes updateVotes: -> votes = $("#votes .votes") notes = $("#notes-list .note .vote") @@ -18,5 +18,3 @@ class NotesVotes # replace vote numbers votes.find(".upvotes").text votes.find(".upvotes").text().replace(/\d+/, upvotes) votes.find(".downvotes").text votes.find(".downvotes").text().replace(/\d+/, downvotes) - -@NotesVotes = NotesVotes diff --git a/app/assets/javascripts/password_strength.js.coffee b/app/assets/javascripts/password_strength.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..825f56302661229a7ab620d68c22c57fe769db2f --- /dev/null +++ b/app/assets/javascripts/password_strength.js.coffee @@ -0,0 +1,31 @@ +#= require pwstrength-bootstrap-1.2.2 +overwritten_messages = + wordSimilarToUsername: "Your password should not contain your username" + +overwritten_rules = + wordSequences: false + +options = + showProgressBar: false + showVerdicts: false + showPopover: true + showErrors: true + showStatus: true + errorMessages: overwritten_messages + +$(document).ready -> + profileOptions = {} + profileOptions.ui = options + profileOptions.rules = + activated: overwritten_rules + + deviseOptions = {} + deviseOptions.common = + usernameField: "#user_username" + deviseOptions.ui = options + deviseOptions.rules = + activated: overwritten_rules + + $("#user_password_profile").pwstrength profileOptions + $("#user_password_sign_up").pwstrength deviseOptions + $("#user_password_recover").pwstrength deviseOptions diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index 0e99921f899fdedf560249e8f709dc0e936d6427..de356fbec77f9c0f243fa1fabebdd99afeb4c07a 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -1,30 +1,29 @@ -$ -> - $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click -> - # Submit the form - $('.edit_user').submit() +class @Profile + constructor: -> + $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click -> + # Submit the form + $('.edit_user').submit() - new Flash("Appearance settings saved", "notice") + new Flash("Appearance settings saved", "notice") - $('.update-username form').on 'ajax:before', -> - $('.loading-gif').show() - $(this).find('.update-success').hide() - $(this).find('.update-failed').hide() + $('.update-username form').on 'ajax:before', -> + $('.loading-gif').show() + $(this).find('.update-success').hide() + $(this).find('.update-failed').hide() - $('.update-username form').on 'ajax:complete', -> - $(this).find('.btn-save').enableButton() - $(this).find('.loading-gif').hide() + $('.update-username form').on 'ajax:complete', -> + $(this).find('.btn-save').enableButton() + $(this).find('.loading-gif').hide() - $('.update-notifications').on 'ajax:complete', -> - $(this).find('.btn-save').enableButton() + $('.update-notifications').on 'ajax:complete', -> + $(this).find('.btn-save').enableButton() - $('.js-choose-user-avatar-button').bind "click", -> - form = $(this).closest("form") - form.find(".js-user-avatar-input").click() + $('.js-choose-user-avatar-button').bind "click", -> + form = $(this).closest("form") + form.find(".js-user-avatar-input").click() - $('.js-user-avatar-input').bind "change", -> - form = $(this).closest("form") - filename = $(this).val().replace(/^.*[\\\/]/, '') - form.find(".js-avatar-filename").text(filename) - - $('.profile-groups-avatars').tooltip("placement": "top") + $('.js-user-avatar-input').bind "change", -> + form = $(this).closest("form") + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find(".js-avatar-filename").text(filename) diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 95c48cd2d5be80c53d3f9b5df54da59b27f484a8..ca120ce91b551ac37ffd007d68a015de8901977b 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -1,70 +1,20 @@ -class Project +class @Project constructor: -> - $('.project-edit-container').on 'ajax:before', => - $('.project-edit-container').hide() - $('.save-project-loader').show() - - @initEvents() - - - initEvents: -> - disableButtonIfEmptyField '#project_name', '.project-submit' - - $('#project_issues_enabled').change -> - if ($(this).is(':checked') == true) - $('#project_issues_tracker').removeAttr('disabled') - else - $('#project_issues_tracker').attr('disabled', 'disabled') - - $('#project_issues_tracker').change() - - $('#project_issues_tracker').change -> - if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled')) - $('#project_issues_tracker_id').attr('disabled', 'disabled') - else - $('#project_issues_tracker_id').removeAttr('disabled') - - $('#project_merge_requests_enabled').change -> - if ($(this).is(':checked') == true) - $('#project_merge_requests_template').removeAttr('disabled') - else - $('#project_merge_requests_template').attr('disabled', 'disabled') - - $('#project_merge_requests_template').change() - - -@Project = Project - -$ -> - # Git clone panel switcher - scope = $ '.git-clone-holder' - if scope.length > 0 - $('a, button', scope).click -> - $('a, button', scope).removeClass 'active' - $(@).addClass 'active' - $('#project_clone', scope).val $(@).data 'clone' - $(".clone").text("").append $(@).data 'clone' - - # Ref switcher - $('.project-refs-select').on 'change', -> - $(@).parents('form').submit() - - $('.hide-no-ssh-message').on 'click', (e) -> - path = '/' - $.cookie('hide_no_ssh_message', 'false', { path: path }) - $(@).parents('.no-ssh-key-message').hide() - e.preventDefault() - - $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) -> - $(@).toggleClass('on').find('.count').html(data.star_count) - .on 'ajax:error', (e, xhr, status, error) -> - new Flash('Star toggle failed. Try again later.', 'alert') - - $("a[data-toggle='tab']").on "shown.bs.tab", (e) -> - $.cookie "default_view", $(e.target).attr("href") - - defaultView = $.cookie("default_view") - if defaultView - $("a[href=" + defaultView + "]").tab "show" - else - $("a[data-toggle='tab']:first").tab "show" + # Git clone panel switcher + scope = $ '.git-clone-holder' + if scope.length > 0 + $('a, button', scope).click -> + $('a, button', scope).removeClass 'active' + $(@).addClass 'active' + $('#project_clone', scope).val $(@).data 'clone' + $(".clone").text("").append $(@).data 'clone' + + # Ref switcher + $('.project-refs-select').on 'change', -> + $(@).parents('form').submit() + + $('.hide-no-ssh-message').on 'click', (e) -> + path = '/' + $.cookie('hide_no_ssh_message', 'false', { path: path }) + $(@).parents('.no-ssh-key-message').hide() + e.preventDefault() \ No newline at end of file diff --git a/app/assets/javascripts/project_import.js.coffee b/app/assets/javascripts/project_import.js.coffee index 7cf44da99fe9ae14699d730047fca19d694dc0b6..6633564a0792c6c877737cd72d0bd4d0daa2d268 100644 --- a/app/assets/javascripts/project_import.js.coffee +++ b/app/assets/javascripts/project_import.js.coffee @@ -1,7 +1,5 @@ -class ProjectImport +class @ProjectImport constructor: -> setTimeout -> Turbolinks.visit(location.href) , 5000 - -@ProjectImport = ProjectImport diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..f4a2ca813d2912368f8096115eb1a25e3d5ee50f --- /dev/null +++ b/app/assets/javascripts/project_new.js.coffee @@ -0,0 +1,25 @@ +class @ProjectNew + constructor: -> + $('.project-edit-container').on 'ajax:before', => + $('.project-edit-container').hide() + $('.save-project-loader').show() + + @initEvents() + + + initEvents: -> + disableButtonIfEmptyField '#project_name', '.project-submit' + + $('#project_issues_enabled').change -> + if ($(this).is(':checked') == true) + $('#project_issues_tracker').removeAttr('disabled') + else + $('#project_issues_tracker').attr('disabled', 'disabled') + + $('#project_issues_tracker').change() + + $('#project_issues_tracker').change -> + if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled')) + $('#project_issues_tracker_id').attr('disabled', 'disabled') + else + $('#project_issues_tracker_id').removeAttr('disabled') diff --git a/app/assets/javascripts/project_show.js.coffee b/app/assets/javascripts/project_show.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..02a7d7b731d71023f2817dbb9cb10ffd0de5e290 --- /dev/null +++ b/app/assets/javascripts/project_show.js.coffee @@ -0,0 +1,15 @@ +class @ProjectShow + constructor: -> + $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) -> + $(@).toggleClass('on').find('.count').html(data.star_count) + .on 'ajax:error', (e, xhr, status, error) -> + new Flash('Star toggle failed. Try again later.', 'alert') + + $("a[data-toggle='tab']").on "shown.bs.tab", (e) -> + $.cookie "default_view", $(e.target).attr("href") + + defaultView = $.cookie("default_view") + if defaultView + $("a[href=" + defaultView + "]").tab "show" + else + $("a[data-toggle='tab']:first").tab "show" diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee index cfbcd5108c83237dd3d7103ed4ba479df12e6296..7fb33926096c1b86e3490f7337ed821863b44bd0 100644 --- a/app/assets/javascripts/project_users_select.js.coffee +++ b/app/assets/javascripts/project_users_select.js.coffee @@ -1,6 +1,6 @@ -@projectUsersSelect = - init: -> - $('.ajax-project-users-select').each (i, select) -> +class @ProjectUsersSelect + constructor: -> + $('.ajax-project-users-select').each (i, select) => project_id = $(select).data('project-id') || $('body').data('project-id') $(select).select2 @@ -28,14 +28,16 @@ Api.user(id, callback) - formatResult: projectUsersSelect.projectUserFormatResult - formatSelection: projectUsersSelect.projectUserFormatSelection + formatResult: (args...) => + @formatResult(args...) + formatSelection: (args...) => + @formatSelection(args...) dropdownCssClass: "ajax-project-users-dropdown" dropdownAutoWidth: true escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results m - projectUserFormatResult: (user) -> + formatResult: (user) -> if user.avatar_url avatar = user.avatar_url else @@ -52,8 +54,5 @@ <div class='user-username'>#{user.username}</div> </div>" - projectUserFormatSelection: (user) -> + formatSelection: (user) -> user.name - -$ -> - projectUsersSelect.init() diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index e144dfa1d68d2371bd9415e08ff3700175c14813..c1801365266f1f63f7f353f93848e8472cd013b1 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -1,4 +1,4 @@ -class SearchAutocomplete +class @SearchAutocomplete constructor: (search_autocomplete_path, project_id, project_ref) -> project_id = '' unless project_id project_ref = '' unless project_ref @@ -9,5 +9,3 @@ class SearchAutocomplete minLength: 1 select: (event, ui) -> location.href = ui.item.url - -@SearchAutocomplete = SearchAutocomplete diff --git a/app/assets/javascripts/stat_graph.js.coffee b/app/assets/javascripts/stat_graph.js.coffee index b129619696fe5a69cc66e50d5884c7d2f669c8b0..f36c71fd25e51f113c721019a9cc3badfdfcdfc2 100644 --- a/app/assets/javascripts/stat_graph.js.coffee +++ b/app/assets/javascripts/stat_graph.js.coffee @@ -1,4 +1,4 @@ -class window.StatGraph +class @StatGraph @log: {} @get_log: -> @log diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/stat_graph_contributors.js.coffee index ab785a5454328452bf89ebad191b5e12036cd1d5..27f0fd31d5025491e46ea9bcb3104c9dc4e12043 100644 --- a/app/assets/javascripts/stat_graph_contributors.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors.js.coffee @@ -1,4 +1,4 @@ -class window.ContributorsStatGraph +class @ContributorsStatGraph init: (log) -> @parsed_log = ContributorsStatGraphUtil.parse_log(log) @set_current_field("commits") diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee index 834c7e5dab03131e41d5b33f9a9a8d48f6a23e54..9952fa0b00abd206729c3e4d973f3ff7544bdb44 100644 --- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee @@ -1,4 +1,4 @@ -class window.ContributorsGraph +class @ContributorsGraph MARGIN: top: 20 right: 20 @@ -44,7 +44,7 @@ class window.ContributorsGraph set_data: (data) -> @data = data -class window.ContributorsMasterGraph extends ContributorsGraph +class @ContributorsMasterGraph extends ContributorsGraph constructor: (@data) -> @width = $('.container').width() - 70 @height = 200 @@ -117,7 +117,7 @@ class window.ContributorsMasterGraph extends ContributorsGraph @svg.select("path").attr("d", @area) @svg.select(".y.axis").call(@y_axis) -class window.ContributorsAuthorGraph extends ContributorsGraph +class @ContributorsAuthorGraph extends ContributorsGraph constructor: (@data) -> @width = $('.container').width()/2 - 100 @height = 200 diff --git a/app/assets/javascripts/team_members.js.coffee b/app/assets/javascripts/team_members.js.coffee deleted file mode 100644 index 5eaa8ad4ff9d761c236ad0816312b4af722bc993..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/team_members.js.coffee +++ /dev/null @@ -1,6 +0,0 @@ -class TeamMembers - constructor: -> - $('.team-members .project-access-select').on "change", -> - $(this.form).submit() - -@TeamMembers = TeamMembers diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee index 4852e879b68ee5d810195d3d704981a726b972e9..d428db5b4227096ab1481fe345f6d4f764a49bb7 100644 --- a/app/assets/javascripts/tree.js.coffee +++ b/app/assets/javascripts/tree.js.coffee @@ -1,4 +1,4 @@ -class TreeView +class @TreeView constructor: -> @initKeyNav() @@ -39,5 +39,3 @@ class TreeView else if e.which is 13 path = $('.tree-item.selected .tree-item-file-name a').attr('href') Turbolinks.visit(path) - -@TreeView = TreeView diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..8a2e2421c2eb454bde693a10c0aa4386a888fd35 --- /dev/null +++ b/app/assets/javascripts/user.js.coffee @@ -0,0 +1,3 @@ +class @User + constructor: -> + $('.profile-groups-avatars').tooltip("placement": "top") diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 08e40a1c41956a3a47ec6f6eef73a8046d3fa6db..10e160fbac0a95cc99e6f07d037dfe02f35abeb1 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -1,5 +1,32 @@ -$ -> - userFormatResult = (user) -> +class @UsersSelect + constructor: -> + $('.ajax-users-select').each (i, select) => + skip_ldap = $(select).hasClass('skip_ldap') + + $(select).select2 + placeholder: "Search for a user" + multiple: $(select).hasClass('multiselect') + minimumInputLength: 0 + query: (query) -> + Api.users query.term, skip_ldap, (users) -> + data = { results: users } + query.callback(data) + + initSelection: (element, callback) -> + id = $(element).val() + if id isnt "" + Api.user(id, callback) + + + formatResult: (args...) => + @formatResult(args...) + formatSelection: (args...) => + @formatSelection(args...) + dropdownCssClass: "ajax-users-dropdown" + escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results + m + + formatResult: (user) -> if user.avatar_url avatar = user.avatar_url else @@ -11,29 +38,5 @@ $ -> <div class='user-username'>#{user.username}</div> </div>" - userFormatSelection = (user) -> - user.name - - $('.ajax-users-select').each (i, select) -> - skip_ldap = $(select).hasClass('skip_ldap') - - $(select).select2 - placeholder: "Search for a user" - multiple: $(select).hasClass('multiselect') - minimumInputLength: 0 - query: (query) -> - Api.users query.term, skip_ldap, (users) -> - data = { results: users } - query.callback(data) - - initSelection: (element, callback) -> - id = $(element).val() - if id isnt "" - Api.user(id, callback) - - - formatResult: userFormatResult - formatSelection: userFormatSelection - dropdownCssClass: "ajax-users-dropdown" - escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results - m + formatSelection: (user) -> + user.name \ No newline at end of file diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee index 17e790e5b7c948db516d5ee7df84672ab03e7fb3..66757565d3aae583405e2e6ef1aebc5602cad83c 100644 --- a/app/assets/javascripts/wikis.js.coffee +++ b/app/assets/javascripts/wikis.js.coffee @@ -1,4 +1,4 @@ -class Wikis +class @Wikis constructor: -> $('.build-new-wiki').bind "click", -> field = $('#new_wiki_path') @@ -7,6 +7,3 @@ class Wikis if(slug.length > 0) location.href = path + "/" + slug - - -@Wikis = Wikis diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss index be4c4d07f1c875b27a1484a2404db440047a9604..469f4f296ae6e1ebe6b9a6e1dd2d5d961cc0b82d 100644 --- a/app/assets/stylesheets/behaviors.scss +++ b/app/assets/stylesheets/behaviors.scss @@ -1,12 +1,22 @@ // Details //-------- -.js-details-container .content { display: none; } -.js-details-container .content.hide { display: block; } -.js-details-container.open .content { display: block; } -.js-details-container.open .content.hide { display: none; } +.js-details-container { + .content { + display: none; + &.hide { display: block; } + } + &.open .content { + display: block; + &.hide { display: none; } + } +} // Toggle between two states. -.js-toggler-container .turn-on { display: block; } -.js-toggler-container .turn-off { display: none; } -.js-toggler-container.on .turn-on { display: none; } -.js-toggler-container.on .turn-off { display: block; } +.js-toggler-container { + .turn-on { display: block; } + .turn-off { display: none; } + &.on { + .turn-on { display: none; } + .turn-off { display: block; } + } +} diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 815cf367ae855c1c590ee53f4ccd81083a2194af..8d5822937a0c9b624bc75c1294a544e18f97f1df 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -186,3 +186,11 @@ } } } + +.readme-holder .wiki, .note-body, .wiki-holder { + .white { + .highlight, pre, .hljs { + background: #F9F9F9; + } + } +} diff --git a/app/assets/stylesheets/main/fonts.scss b/app/assets/stylesheets/main/fonts.scss index d90274a0db92aa8634407d1f63123a5b95c9b3e9..f945aaca8484b3f4bc4ce0d416470379b387e397 100644 --- a/app/assets/stylesheets/main/fonts.scss +++ b/app/assets/stylesheets/main/fonts.scss @@ -1,3 +1,3 @@ /** Typo **/ -$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; +$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; $regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif; diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index 086875582f396c9fac6f540d93fb5f32c9766745..b9f4e317e9c7db053842d9aa7e2188dee026f97c 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -111,3 +111,20 @@ height: 50px; } } + +//CSS for password-strength indicator +#password-strength { + margin-bottom: 0; +} + +.has-success input { + background-color: #D6F1D7 !important; +} + +.has-error input { + background-color: #F3CECE !important; +} + +.has-warning input { + background-color: #FFE9A4 !important; +} diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb index 4c1d0df4110e3345074dc39df88de2416caf0798..338496013a02a61954e865b003eaa9daf0377f0f 100644 --- a/app/controllers/admin/background_jobs_controller.rb +++ b/app/controllers/admin/background_jobs_controller.rb @@ -1,6 +1,6 @@ class Admin::BackgroundJobsController < Admin::ApplicationController def show - ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command)) + ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Gitlab.config.gitlab.user} -o pid,pcpu,pmem,stat,start,command)) @sidekiq_processes = ps_output.split("\n").grep(/sidekiq/) end end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 2f0d344802f2caf0e283b24daafd4974da7b4ab4..7c2388e81be847b1ee1e85ea8c83cac040981fcd 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -31,17 +31,11 @@ class Admin::ProjectsController < Admin::ApplicationController protected def project - id = params[:project_id] || params[:id] - - @project = Project.find_with_namespace(id) + @project = Project.find_with_namespace(params[:id]) @project || render_404 end def group - @group ||= project.group - end - - def repository - @repository ||= project.repository + @group ||= @project.group end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 13d8d2a3e0ad1ce48c15f5b4bbf1fce3f12e8b3b..f1e1bebe5ce548108e62173229d47e31e5bdce45 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,9 +5,7 @@ class ApplicationController < ActionController::Base before_filter :authenticate_user! before_filter :reject_blocked! before_filter :check_password_expiration - before_filter :add_abilities before_filter :ldap_security_check - before_filter :dev_tools if Rails.env == 'development' before_filter :default_headers before_filter :add_gon_variables before_filter :configure_permitted_parameters, if: :devise_controller? @@ -73,7 +71,7 @@ class ApplicationController < ActionController::Base end def abilities - @abilities ||= Six.new + Ability.abilities end def can?(object, action, subject) @@ -81,28 +79,31 @@ class ApplicationController < ActionController::Base end def project - id = params[:project_id] || params[:id] - - # Redirect from - # localhost/group/project.git - # to - # localhost/group/project - # - if id =~ /\.git\Z/ - redirect_to request.original_url.gsub(/\.git\Z/, '') and return - end + unless @project + id = params[:project_id] || params[:id] + + # Redirect from + # localhost/group/project.git + # to + # localhost/group/project + # + if id =~ /\.git\Z/ + redirect_to request.original_url.gsub(/\.git\Z/, '') and return + end - @project = Project.find_with_namespace(id) + @project = Project.find_with_namespace(id) - if @project and can?(current_user, :read_project, @project) - @project - elsif current_user.nil? - @project = nil - authenticate_user! - else - @project = nil - render_404 and return + if @project and can?(current_user, :read_project, @project) + @project + elsif current_user.nil? + @project = nil + authenticate_user! + else + @project = nil + render_404 and return + end end + @project end def repository @@ -111,22 +112,10 @@ class ApplicationController < ActionController::Base nil end - def add_abilities - abilities << Ability - end - def authorize_project!(action) return access_denied! unless can?(current_user, action, project) end - def authorize_code_access! - return access_denied! unless can?(current_user, :download_code, project) - end - - def authorize_push! - return access_denied! unless can?(current_user, :push_code, project) - end - def authorize_labels! # Labels should be accessible for issues and/or merge requests authorize_read_issue! || authorize_read_merge_request! @@ -170,9 +159,6 @@ class ApplicationController < ActionController::Base response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" end - def dev_tools - end - def default_headers headers['X-Frame-Options'] = 'DENY' headers['X-XSS-Protection'] = '1; mode=block' diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb index f8e1a31e0b325fc3014e9821fb54b99c55aee250..ada7031fea4318add194b9862790e63815e4f92c 100644 --- a/app/controllers/explore/groups_controller.rb +++ b/app/controllers/explore/groups_controller.rb @@ -1,7 +1,6 @@ class Explore::GroupsController < ApplicationController skip_before_filter :authenticate_user!, - :reject_blocked, :set_current_user_for_observers, - :add_abilities + :reject_blocked, :set_current_user_for_observers layout "explore" diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index b6fa8b7e3872659dd10d8fea4c734b0007f5d894..d75fd8e72fa6b3b14b80aab5782f5790d849c7c1 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -1,7 +1,6 @@ class Explore::ProjectsController < ApplicationController skip_before_filter :authenticate_user!, - :reject_blocked, - :add_abilities + :reject_blocked layout 'explore' diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb index 5e30593443311ddd50db57b90433b20e56e9b2ff..a7b1b7b40e84dfd9ab45afb707ef4e5c30884e47 100644 --- a/app/controllers/projects/base_tree_controller.rb +++ b/app/controllers/projects/base_tree_controller.rb @@ -1,8 +1,7 @@ class Projects::BaseTreeController < Projects::ApplicationController include ExtractsPath - before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project end diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index a3c41301676500b053e95472513f99264691b902..367d1295f342221307a069cf6f17e5978aa66df0 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -3,8 +3,7 @@ class Projects::BlameController < Projects::ApplicationController include ExtractsPath # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def show diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 0944c7421ee816c008ad62c457291da001f701a6..2412800c4931394a342c24b44b35bd02b60bb8e4 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -3,10 +3,9 @@ class Projects::BlobController < Projects::ApplicationController include ExtractsPath # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project - before_filter :authorize_push!, only: [:destroy] + before_filter :authorize_push_code!, only: [:destroy] before_filter :blob diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index faa0ce67ca894408dcb541b64ef2c466661cdfd7..9ebd498e7faeddcdf1e95d68ff178aca61370f09 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -1,10 +1,9 @@ class Projects::BranchesController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! before_filter :require_non_empty_project - before_filter :authorize_code_access! - before_filter :authorize_push!, only: [:create, :destroy] + before_filter :authorize_download_code! + before_filter :authorize_push_code!, only: [:create, :destroy] def index @sort = params[:sort] || 'name' @@ -19,6 +18,7 @@ class Projects::BranchesController < Projects::ApplicationController def create result = CreateBranchService.new(project, current_user). execute(params[:branch_name], params[:ref]) + if result[:status] == :success @branch = result[:branch] redirect_to project_tree_path(@project, @branch.name) diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 66c67b661dbe92c9d19f769c1d01bc07b99baeae..dac858d8e16e8986980c66918236518031974744 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -3,20 +3,19 @@ # Not to be confused with CommitsController, plural. class Projects::CommitController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project before_filter :commit def show return git_not_found! unless @commit - @line_notes = project.notes.for_commit_id(commit.id).inline - @branches = project.repository.branch_names_contains(commit.id) + @line_notes = @project.notes.for_commit_id(commit.id).inline + @branches = @project.repository.branch_names_contains(commit.id) @diffs = @commit.diffs - @note = project.build_commit_note(commit) - @notes_count = project.notes.for_commit_id(commit.id).count - @notes = project.notes.for_commit_id(@commit.id).not_inline.fresh + @note = @project.build_commit_note(commit) + @notes_count = @project.notes.for_commit_id(commit.id).count + @notes = @project.notes.for_commit_id(@commit.id).not_inline.fresh @noteable = @commit @comments_allowed = @reply_allowed = true @comments_target = { @@ -32,6 +31,6 @@ class Projects::CommitController < Projects::ApplicationController end def commit - @commit ||= project.repository.commit(params[:id]) + @commit ||= @project.repository.commit(params[:id]) end end diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index b7f09eb271dddc799539f435b994e36abc4eae65..9476b6c0284954281b16556a93a8465677689749 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -4,8 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController include ExtractsPath # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def show diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 7a671e8455dd07f65e57f2861c2b00e101669f44..ffb8c2e4af16b8f523928ebd03a73702b2652e77 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -1,7 +1,6 @@ class Projects::CompareController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def index diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index d20937ea8ea5e174282a4a160907e4199be31523..024b9520d30aaaff126e6c42796009dc31028255 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -42,7 +42,7 @@ class Projects::DeployKeysController < Projects::ApplicationController end def enable - project.deploy_keys << available_keys.find(params[:id]) + @project.deploy_keys << available_keys.find(params[:id]) redirect_to project_deploy_keys_path(@project) end diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb index fdc1a85d8d7d932f65b6f7e135938f49badb7d4a..65661c80410b66e22b578f1e78d6bfb64dea1ddb 100644 --- a/app/controllers/projects/edit_tree_controller.rb +++ b/app/controllers/projects/edit_tree_controller.rb @@ -1,7 +1,7 @@ class Projects::EditTreeController < Projects::BaseTreeController before_filter :require_branch_head before_filter :blob - before_filter :authorize_push! + before_filter :authorize_push_code! before_filter :from_merge_request before_filter :after_edit_path diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 610b4967fea974ad20c49bb7b8b7e660029e4872..4a318cb7d5681939ad2d9e7c3978c77c8098d041 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -1,7 +1,6 @@ class Projects::GraphsController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def show diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb index 9832495c64fb3a1d177b37434d37225d59272d90..ada1aed0df789656da87ea3ae2e9e7419d524b9c 100644 --- a/app/controllers/projects/network_controller.rb +++ b/app/controllers/projects/network_controller.rb @@ -3,8 +3,7 @@ class Projects::NetworkController < Projects::ApplicationController include ApplicationHelper # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def show diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb index 71a5c6499ecdf9eaf68e8fe97468be8d5789a422..ffba706b2f665223dae58078bf5e45bf381210e3 100644 --- a/app/controllers/projects/new_tree_controller.rb +++ b/app/controllers/projects/new_tree_controller.rb @@ -1,6 +1,6 @@ class Projects::NewTreeController < Projects::BaseTreeController before_filter :require_branch_head - before_filter :authorize_push! + before_filter :authorize_push_code! def show end diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 5ec9c576a66c44378bdba828e21c322756dd972a..fdbc4c5a0984adb48193779ff3c56121e5a1ea75 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -3,8 +3,7 @@ class Projects::RawController < Projects::ApplicationController include ExtractsPath # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def show diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 7997c726fbb8f36a554cce33193c25afb2936f83..5d9336bdc49b774782c307a8ab38f3d7886f5ce5 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -2,8 +2,7 @@ class Projects::RefsController < Projects::ApplicationController include ExtractsPath # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def switch diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 4e0f190ed1cdb345448425f518cf59b2198c6ff8..bcd14a1c847019f67405730a325c7c109557d6a1 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -1,7 +1,6 @@ class Projects::RepositoriesController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def archive diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 28c62f5ec72254a0f79844650cc28eed3810ee84..a5f30dcfd9d2a553a01323a65390ff74281be790 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -40,8 +40,9 @@ class Projects::ServicesController < Projects::ApplicationController def service_params params.require(:service).permit( :title, :token, :type, :active, :api_key, :subdomain, - :room, :recipients, :project_url, :webhook, :username, :password, :api_version, - :user_key, :device, :priority, :sound + :room, :recipients, :project_url, :webhook, + :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, + :build_key ) end end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 537c94bda20e956e19a9233a381e98862d6bfc69..162ddef0fec47239ad4c49a929e25f16ac1c4dd0 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -1,10 +1,8 @@ class Projects::TagsController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! before_filter :require_non_empty_project - - before_filter :authorize_code_access! - before_filter :authorize_push!, only: [:create] + before_filter :authorize_download_code! + before_filter :authorize_push_code!, only: [:create] before_filter :authorize_admin_project!, only: [:destroy] def index diff --git a/app/controllers/projects/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb index f61a072f5fb3b31eb573b01c6346fd5782a5e815..c364814681f635db2d12c40b2712458ea16673a9 100644 --- a/app/controllers/projects/team_members_controller.rb +++ b/app/controllers/projects/team_members_controller.rb @@ -11,7 +11,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def new - @user_project_relation = project.project_members.new + @user_project_relation = @project.project_members.new end def create @@ -27,7 +27,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def update - @user_project_relation = project.project_members.find_by(user_id: member) + @user_project_relation = @project.project_members.find_by(user_id: member) @user_project_relation.update_attributes(member_params) unless @user_project_relation.valid? @@ -37,7 +37,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def destroy - @user_project_relation = project.project_members.find_by(user_id: member) + @user_project_relation = @project.project_members.find_by(user_id: member) @user_project_relation.destroy respond_to do |format| @@ -47,7 +47,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def leave - project.project_members.find_by(user_id: current_user).destroy + @project.project_members.find_by(user_id: current_user).destroy respond_to do |format| format.html { redirect_to :back } diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index f0784324d72cf66c844f2bb422a1c165321f239c..3f29240c88dd94df8610b3ed03467451665e2163 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -4,9 +4,7 @@ class ProjectsController < ApplicationController before_filter :repository, except: [:new, :create] # Authorize - before_filter :authorize_read_project!, except: [:index, :new, :create] before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import] - before_filter :require_non_empty_project, only: [:blob, :tree, :graph] layout 'navless', only: [:new, :create, :fork] before_filter :set_title, only: [:new, :create] @@ -53,8 +51,6 @@ class ProjectsController < ApplicationController return end - return authenticate_user! unless @project.public? || current_user - limit = (params[:limit] || 20).to_i @events = @project.events.recent @events = event_filter.apply_filter(@events) @@ -76,7 +72,7 @@ class ProjectsController < ApplicationController end def import - if project.import_finished? + if @project.import_finished? redirect_to @project return end @@ -98,7 +94,7 @@ class ProjectsController < ApplicationController end def destroy - return access_denied! unless can?(current_user, :remove_project, project) + return access_denied! unless can?(current_user, :remove_project, @project) ::Projects::DestroyService.new(@project, current_user, {}).execute @@ -148,8 +144,8 @@ class ProjectsController < ApplicationController end def archive - return access_denied! unless can?(current_user, :archive_project, project) - project.archive! + return access_denied! unless can?(current_user, :archive_project, @project) + @project.archive! respond_to do |format| format.html { redirect_to @project } @@ -157,8 +153,8 @@ class ProjectsController < ApplicationController end def unarchive - return access_denied! unless can?(current_user, :archive_project, project) - project.unarchive! + return access_denied! unless can?(current_user, :archive_project, @project) + @project.unarchive! respond_to do |format| format.html { redirect_to @project } diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 56c4f22120dd880095cebc61441c422978b12d53..d05742405112f3d8e6a78233739c026a52d2858f 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -48,7 +48,7 @@ class IssuableFinder else [] end - elsif current_user && params[:authorized_only].presence + elsif current_user && params[:authorized_only].presence && !current_user_related? klass.of_projects(current_user.authorized_projects).references(:project) else klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project) @@ -142,4 +142,8 @@ class IssuableFinder def project Project.where(id: params[:project_id]).first if params[:project_id].present? end + + def current_user_related? + params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' + end end diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..24d67c21d6bbc1be68fe8618d2a9764a5d326cfa --- /dev/null +++ b/app/helpers/emails_helper.rb @@ -0,0 +1,32 @@ +module EmailsHelper + + # Google Actions + # https://developers.google.com/gmail/markup/reference/go-to-action + def email_action(url) + name = action_title(url) + if name + data = { + "@context" => "http://schema.org", + "@type" => "EmailMessage", + "action" => { + "@type" => "ViewAction", + "name" => name, + "url" => url, + } + } + + content_tag :script, type: 'application/ld+json' do + data.to_json.html_safe + end + end + end + + def action_title(url) + return unless url + ["merge_requests", "issues", "commit"].each do |action| + if url.split("/").include?(action) + return "View #{action.humanize.singularize}" + end + end + end +end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 7671033b539c90455d59a180ebaffb6ed490474c..d513e0ba58ef9a8ca5bdd3a203582fdaffa17092 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -62,6 +62,19 @@ module IssuesHelper '' end + def issue_timestamp(issue) + # Shows the created at time and the updated at time if different + ts = "#{time_ago_with_tooltip(issue.created_at, 'bottom', 'note_created_ago')}" + if issue.updated_at != issue.created_at + ts << capture_haml do + haml_tag :small do + haml_concat " (Edited #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago')})" + end + end + end + ts.html_safe + end + # Checks if issues_tracker setting exists in gitlab.yml def external_issues_tracker_enabled? Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any? diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 883c1f63af69fa5073f195362a90d777f5ebe142..fb5470d98e57c2caf34382619fbdd6716a0a5ee3 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -42,12 +42,12 @@ module ProjectsHelper def project_title(project) if project.group content_tag :span do - link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name + link_to(simple_sanitize(project.group.name), group_path(project.group)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project)) end else owner = project.namespace.owner content_tag :span do - link_to(simple_sanitize(owner.name), user_path(owner)) + " / " + project.name + link_to(simple_sanitize(owner.name), user_path(owner)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project)) end end end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 9c611a1c147c5ba06e0b35ac5ebb3c460b413c94..8e2094983231c4c9bd2b2ac0fc6eb4e6c1e7cc3e 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -66,7 +66,7 @@ module TreeHelper def tree_breadcrumbs(tree, max_links = 2) if @path.present? part_path = "" - parts = @path.split("\/") + parts = @path.split('/') yield('..', nil) if parts.count > max_links diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 1b65e7746e3e1084dab1bc546244eebe8a6b4079..562835b9ce826ca5255f75bf348aeddd5f8a53ce 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -12,6 +12,7 @@ class Notify < ActionMailer::Base add_template_helper ApplicationHelper add_template_helper GitlabMarkdownHelper add_template_helper MergeRequestsHelper + add_template_helper EmailsHelper default_url_options[:host] = Gitlab.config.gitlab.host default_url_options[:protocol] = Gitlab.config.gitlab.protocol diff --git a/app/models/ability.rb b/app/models/ability.rb index e155abc1449cf7a9d4996baeb066d8d32cd484b1..97a72bf3635b17ae61d5fab57284c9fd632c7762 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -262,5 +262,13 @@ class Ability end rules end + + def abilities + @abilities ||= begin + abilities = Six.new + abilities << self + abilities + end + end end end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 553087946d67239186a878015608160e078c2c54..f49708fd6eb36db6c3c10f983f725b821aac054d 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -131,9 +131,10 @@ module Issuable users.concat(mentions.reduce([], :|)).uniq end - def to_hook_data + def to_hook_data(user) { object_kind: self.class.name.underscore, + user: user.hook_attrs, object_attributes: hook_attrs } end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 5938d9cb28eefee136e2a959dd50e4a481f60853..6c1aa99668a43c433a4a4ab2024731e624761658 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -52,11 +52,7 @@ module Mentionable if identifier == "all" users += project.team.members.flatten else - if has_project - id = project.team.members.find_by(username: identifier).try(:id) - else - id = User.find_by(username: identifier).try(:id) - end + id = User.find_by(username: identifier).try(:id) users << User.find(id) unless id.blank? end end diff --git a/app/models/event.rb b/app/models/event.rb index c0b126713a6eac24ee31c08042e92c235289803f..65b4c2edfee043d343707218f95b2e9993805850 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -186,10 +186,6 @@ class Event < ActiveRecord::Base data[:ref]["refs/heads"] end - def new_branch? - commit_from =~ /^00000/ - end - def new_ref? commit_from =~ /^00000/ end diff --git a/app/models/note.rb b/app/models/note.rb index 6f1b1a4da94346be616303664b6597a3a0384adf..5bf645bbd1d9e140d3fb3cf2ffbfca3389702aeb 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -80,7 +80,7 @@ class Note < ActiveRecord::Base note_options = { project: project, author: author, - note: "_mentioned in #{gfm_reference}_", + note: cross_reference_note_content(gfm_reference), system: true } @@ -90,7 +90,7 @@ class Note < ActiveRecord::Base note_options.merge!(noteable: noteable) end - create(note_options) + create(note_options) unless cross_reference_disallowed?(noteable, mentioner) end def create_milestone_change_note(noteable, project, author, milestone) @@ -165,6 +165,15 @@ class Note < ActiveRecord::Base [:discussion, type.try(:underscore), id, line_code].join("-").to_sym end + # Determine if cross reference note should be created. + # eg. mentioning a commit in MR comments which exists inside a MR + # should not create "mentioned in" note. + def cross_reference_disallowed?(noteable, mentioner) + if mentioner.kind_of?(MergeRequest) + mentioner.commits.map(&:id).include? noteable.id + end + end + # Determine whether or not a cross-reference note already exists. def cross_reference_exists?(noteable, mentioner) gfm_reference = mentioner_gfm_ref(noteable, mentioner) @@ -174,7 +183,7 @@ class Note < ActiveRecord::Base where(noteable_id: noteable.id) end - notes.where('note like ?', "_mentioned in #{gfm_reference}_"). + notes.where('note like ?', cross_reference_note_content(gfm_reference)). system.any? end @@ -182,8 +191,16 @@ class Note < ActiveRecord::Base where("note like :query", query: "%#{query}%") end + def cross_reference_note_prefix + '_mentioned in ' + end + private + def cross_reference_note_content(gfm_reference) + cross_reference_note_prefix + "#{gfm_reference}_" + end + # Prepend the mentioner's namespaced project path to the GFM reference for # cross-project references. For same-project references, return the # unmodified GFM reference. @@ -243,12 +260,16 @@ class Note < ActiveRecord::Base def commit_author @commit_author ||= - project.users.find_by(email: noteable.author_email) || - project.users.find_by(name: noteable.author_name) + project.team.users.find_by(email: noteable.author_email) || + project.team.users.find_by(name: noteable.author_name) rescue nil end + def cross_reference? + note.start_with?(self.class.cross_reference_note_prefix) + end + def find_diff return nil unless noteable && noteable.diffs.present? @@ -296,7 +317,7 @@ class Note < ActiveRecord::Base end def diff_file_index - line_code.split('_')[0] + line_code.split('_')[0] if line_code end def diff_file_name @@ -312,11 +333,11 @@ class Note < ActiveRecord::Base end def diff_old_line - line_code.split('_')[1].to_i + line_code.split('_')[1].to_i if line_code end def diff_new_line - line_code.split('_')[2].to_i + line_code.split('_')[2].to_i if line_code end def generate_line_code(line) @@ -337,6 +358,20 @@ class Note < ActiveRecord::Base @diff_line end + def diff_line_type + return @diff_line_type if @diff_line_type + + if diff + diff_lines.each do |line| + if generate_line_code(line) == self.line_code + @diff_line_type = line.type + end + end + end + + @diff_line_type + end + def truncated_diff_lines max_number_of_lines = 16 prev_match_line = nil diff --git a/app/models/project.rb b/app/models/project.rb index ce2b814b505ad1bd1b1b220b145f76f30eb59a19..0cdd11912a107e559de29171d5ac0e46e099d6b3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -68,6 +68,7 @@ class Project < ActiveRecord::Base has_one :jira_service, dependent: :destroy has_one :jenkins_service, dependent: :destroy has_one :buildbox_service, dependent: :destroy + has_one :bamboo_service, dependent: :destroy has_one :pushover_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" @@ -180,7 +181,7 @@ class Project < ActiveRecord::Base end def with_push - includes(:events).where('events.action = ?', Event::PUSHED) + joins(:events).where('events.action = ?', Event::PUSHED) end def active @@ -320,7 +321,7 @@ class Project < ActiveRecord::Base end def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack jira jenkins pushover buildbox) + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack jira jenkins pushover buildbox bamboo) end def gitlab_ci? @@ -416,43 +417,8 @@ class Project < ActiveRecord::Base end def update_merge_requests(oldrev, newrev, ref, user) - return true unless ref =~ /heads/ - branch_name = ref.gsub("refs/heads/", "") - commits = self.repository.commits_between(oldrev, newrev) - c_ids = commits.map(&:id) - - # Close merge requests - mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a - mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } - - mrs.uniq.each do |merge_request| - MergeRequests::MergeService.new.execute(merge_request, user, nil) - end - - # Update code for merge requests into project between project branches - mrs = self.merge_requests.opened.by_branch(branch_name).to_a - # Update code for merge requests between project and project fork - mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a - - mrs.uniq.each do |merge_request| - merge_request.reload_code - merge_request.mark_as_unchecked - end - - # Add comment about pushing new commits to merge requests - comment_mr_with_commits(branch_name, commits, user) - - true - end - - def comment_mr_with_commits(branch_name, commits, user) - mrs = self.origin_merge_requests.opened.where(source_branch: branch_name).to_a - mrs += self.fork_merge_requests.opened.where(source_branch: branch_name).to_a - - mrs.uniq.each do |merge_request| - Note.create_new_commits_note(merge_request, merge_request.project, - user, commits) - end + MergeRequests::RefreshService.new(self, user). + execute(oldrev, newrev, ref) end def valid_repo? diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..b9eec9ab21e9a9f28028b43cfb32ba4962b7597c --- /dev/null +++ b/app/models/project_services/bamboo_service.rb @@ -0,0 +1,105 @@ +class BambooService < CiService + include HTTParty + + prop_accessor :bamboo_url, :build_key, :username, :password + + validates :bamboo_url, presence: true, + format: { with: URI::regexp }, if: :activated? + validates :build_key, presence: true, if: :activated? + validates :username, presence: true, + if: ->(service) { service.password? }, if: :activated? + validates :password, presence: true, + if: ->(service) { service.username? }, if: :activated? + + attr_accessor :response + + after_save :compose_service_hook, if: :activated? + + def compose_service_hook + hook = service_hook || build_service_hook + hook.save + end + + def title + 'Atlassian Bamboo CI' + end + + def description + 'A continuous integration and build server' + end + + def help + 'You must set up automatic revision labeling and a repository trigger in Bamboo.' + end + + def to_param + 'bamboo' + end + + def fields + [ + { type: 'text', name: 'bamboo_url', + placeholder: 'Bamboo root URL like https://bamboo.example.com' }, + { type: 'text', name: 'build_key', + placeholder: 'Bamboo build plan key like KEY' }, + { type: 'text', name: 'username', + placeholder: 'A user with API access, if applicable' }, + { type: 'password', name: 'password' }, + ] + end + + def build_info(sha) + url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}") + + if username.blank? && password.blank? + @response = HTTParty.get(parsed_url.to_s, verify: false) + else + get_url = "#{url}&os_authType=basic" + auth = { + username: username, + password: password, + } + @response = HTTParty.get(get_url, verify: false, basic_auth: auth) + end + end + + def build_page(sha) + build_info(sha) if @response.nil? || !@response.code + + if @response.code != 200 || @response['results']['results']['size'] == '0' + # If actual build link can't be determined, send user to build summary page. + "#{bamboo_url}/browse/#{build_key}" + else + # If actual build link is available, go to build result page. + result_key = @response['results']['results']['result']['planResultKey']['key'] + "#{bamboo_url}/browse/#{result_key}" + end + end + + def commit_status(sha) + build_info(sha) if @response.nil? || !@response.code + return :error unless @response.code == 200 || @response.code == 404 + + status = if @response.code == 404 || @response['results']['results']['size'] == '0' + 'Pending' + else + @response['results']['results']['result']['buildState'] + end + + if status.include?('Success') + 'success' + elsif status.include?('Failed') + 'failed' + elsif status.include?('Pending') + 'pending' + else + :error + end + end + + def execute(_data) + # Bamboo requires a GET and does not take any data. + self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}", + verify: false) + end +end diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 0020b4482e5b8d1bc1d8428208aa81117e313971..86705f5dabd19c4be5835237d83ae82a923512fd 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -37,13 +37,12 @@ class FlowdockService < Service end def execute(push_data) - repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") Flowdock::Git.post( push_data[:ref], push_data[:before], push_data[:after], token: token, - repo: repo_path, + repo: project.repository.path_to_repo, repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}", commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s", diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s", diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 6d2fc06a5d09f705bbedf27957d60c8b5f22f7ba..18fdd204ecd6a901349dba79005f1d34a5d8a288 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -38,14 +38,13 @@ class GemnasiumService < Service end def execute(push_data) - repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") Gemnasium::GitlabService.execute( ref: push_data[:ref], before: push_data[:before], after: push_data[:after], token: token, api_key: api_key, - repo: repo_path + repo: project.repository.path_to_repo ) end end diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index a897c4ab76b369a30e5bc8ac941de35aa576230f..fadebf968bc7fe4f058ad82f7d1a30acf32ebac9 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -28,7 +28,7 @@ class GitlabCiService < CiService end def commit_status_path(sha) - project_url + "/builds/#{sha}/status.json?token=#{token}" + project_url + "/commits/#{sha}/status.json?token=#{token}" end def get_ci_build(sha) @@ -55,7 +55,7 @@ class GitlabCiService < CiService end def build_page(sha) - project_url + "/builds/#{sha}" + project_url + "/commits/#{sha}" end def builds_path diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 837002ef3c87861cea0f5aa54fdef7e543a4a491..963f5440b6f87fef112f7bb7cf5ad7ce2b1740bf 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -30,24 +30,20 @@ class SlackService < Service def fields [ - { type: 'text', name: 'webhook', placeholder: '' } + { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' } ] end def execute(push_data) + return unless webhook.present? + message = SlackMessage.new(push_data.merge( project_url: project_url, project_name: project_name )) - credentials = webhook.match(/([\w-]*).slack.com.*services\/(.*)/) - - if credentials.present? - subdomain = credentials[1] - token = credentials[2].split("token=").last - notifier = Slack::Notifier.new(subdomain, token) - notifier.ping(message.pretext, attachments: message.attachments) - end + notifier = Slack::Notifier.new(webhook) + notifier.ping(message.pretext, attachments: message.attachments) end private diff --git a/app/models/user.rb b/app/models/user.rb index d9b0033fc47ef1cc134631acce2ff736d439bd54..f807f3236793fd19e12db185a31ab5a16fed34ef 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -231,6 +231,11 @@ class User < ActiveRecord::Base where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%") end + def by_login(login) + where('lower(username) = :value OR lower(email) = :value', + value: login.to_s.downcase).first + 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 @@ -336,11 +341,7 @@ class User < ActiveRecord::Base end def abilities - @abilities ||= begin - abilities = Six.new - abilities << Ability - abilities - end + Ability.abilities end def can_select_namespace? @@ -503,6 +504,14 @@ class User < ActiveRecord::Base end end + def hook_attrs + { + name: name, + username: username, + avatar_url: avatar_url + } + end + def ensure_namespace_correct # Ensure user has namespace self.create_namespace!(path: self.username, name: self.username) unless self.namespace diff --git a/app/services/base_service.rb b/app/services/base_service.rb index ed286c0409585d2dd28ca317a71f008aec26583c..0d46eeaa18f11539eb8018ad47f861b26b7cbd6f 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -6,11 +6,7 @@ class BaseService end def abilities - @abilities ||= begin - abilities = Six.new - abilities << Ability - abilities - end + Ability.abilities end def can?(object, action, subject) diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index e5a15d6a23632a2f4f2c64750ad456def9f1c3b5..329ba3a934fc133fab2a601e6ebdd15eb7c1e372 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -83,9 +83,14 @@ class GitPushService # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to # a different branch. issues_to_close = commit.closes_issues(project) - author = commit_user(commit) - if !issues_to_close.empty? && is_default_branch + # Load commit author only if needed. + # For push with 1k commits it prevents 900+ requests in database + author = nil + + if issues_to_close.present? && is_default_branch + author ||= commit_user(commit) + issues_to_close.each do |issue| if project.jira_tracker? && project.jira_service.active project.jira_service.execute(push_data, issue) @@ -100,8 +105,13 @@ class GitPushService # being pushed to a different branch). refs = commit.references(project) - issues_to_close refs.reject! { |r| commit.has_mentioned?(r) } - refs.each do |r| - Note.create_cross_reference_note(r, commit, author, project) + + if refs.present? + author ||= commit_user(commit) + + refs.each do |r| + Note.create_cross_reference_note(r, commit, author, project) + end end end end @@ -164,19 +174,19 @@ class GitPushService ref_parts = ref.split('/') # Return if this is not a push to a branch (e.g. new commits) - ref_parts[1] =~ /heads/ && oldrev != "0000000000000000000000000000000000000000" + ref_parts[1] =~ /heads/ && oldrev != Gitlab::Git::BLANK_SHA end def push_to_new_branch?(ref, oldrev) ref_parts = ref.split('/') - ref_parts[1] =~ /heads/ && oldrev == "0000000000000000000000000000000000000000" + ref_parts[1] =~ /heads/ && oldrev == Gitlab::Git::BLANK_SHA end def push_remove_branch?(ref, newrev) ref_parts = ref.split('/') - ref_parts[1] =~ /heads/ && newrev == "0000000000000000000000000000000000000000" + ref_parts[1] =~ /heads/ && newrev == Gitlab::Git::BLANK_SHA end def push_to_branch?(ref) diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 41948f226a6580a87736a1b843b8b06231a2e4b0..755c0ef45a8be788da850b315c936b303a1ab5db 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -4,7 +4,7 @@ module Issues private def execute_hooks(issue, action = 'open') - issue_data = issue.to_hook_data + issue_data = issue.to_hook_data(current_user) issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id) issue_data[:object_attributes].merge!(url: issue_url, action: action) issue.project.execute_hooks(issue_data, :issue_hooks) diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb index 9bc50d3d16ca29452cfb71ee5b915306f59027b4..700a21ca01150eda33681ae25a1c21adfd0f1458 100644 --- a/app/services/merge_requests/base_merge_service.rb +++ b/app/services/merge_requests/base_merge_service.rb @@ -13,7 +13,8 @@ module MergeRequests def execute_project_hooks(merge_request) if merge_request.project - merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) + hook_data = merge_request.to_hook_data(current_user) + merge_request.project.execute_hooks(hook_data, :merge_request_hooks) end end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 694994001b0dcb8bfea3d9f2929bb29d18edbe85..7f3421b8e4ba9c16dd9ef8a399ffef693427ec9f 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -7,7 +7,8 @@ module MergeRequests def execute_hooks(merge_request) if merge_request.project - merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) + hook_data = merge_request.to_hook_data(current_user) + merge_request.project.execute_hooks(hook_data, :merge_request_hooks) end end end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..74448998dddde385fc25560b16580fb08ee95221 --- /dev/null +++ b/app/services/merge_requests/refresh_service.rb @@ -0,0 +1,67 @@ +module MergeRequests + class RefreshService < MergeRequests::BaseService + def execute(oldrev, newrev, ref) + return true unless ref =~ /heads/ + + @branch_name = ref.gsub("refs/heads/", "") + @fork_merge_requests = @project.fork_merge_requests.opened + @commits = @project.repository.commits_between(oldrev, newrev) + + close_merge_requests + reload_merge_requests + comment_mr_with_commits + + true + end + + private + + # Collect open merge requests that target same branch we push into + # and close if push to master include last commit from merge request + # We need this to close(as merged) merge requests that were merged into + # target branch manually + def close_merge_requests + commit_ids = @commits.map(&:id) + merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a + merge_requests = merge_requests.select(&:last_commit) + + merge_requests = merge_requests.select do |merge_request| + commit_ids.include?(merge_request.last_commit.id) + end + + + merge_requests.uniq.select(&:source_project).each do |merge_request| + MergeRequests::MergeService.new.execute(merge_request, @current_user, nil) + end + end + + # Refresh merge request diff if we push to source or target branch of merge request + # Note: we should update merge requests from forks too + def reload_merge_requests + merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a + merge_requests += @fork_merge_requests.by_branch(@branch_name).to_a + merge_requests = filter_merge_requests(merge_requests) + + merge_requests.each do |merge_request| + merge_request.reload_code + merge_request.mark_as_unchecked + end + end + + # Add comment about pushing new commits to merge requests + def comment_mr_with_commits + merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a + merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a + merge_requests = filter_merge_requests(merge_requests) + + merge_requests.each do |merge_request| + Note.create_new_commits_note(merge_request, merge_request.project, + @current_user, @commits) + end + end + + def filter_merge_requests(merge_requests) + merge_requests.uniq.select(&:source_project) + end + end +end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index fe39f83b400286cb481ef9dbb9c834180283df32..c9a1574b84e10daffc0a3d949b3411dcb9ea8c60 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -119,11 +119,12 @@ class NotificationService # ignore gitlab service messages return true if note.note =~ /\A_Status changed to closed_/ - return true if note.note =~ /\A_mentioned in / && note.system == true + return true if note.cross_reference? && note.system == true opts = { noteable_type: note.noteable_type, project_id: note.project_id } target = note.noteable + if target.respond_to?(:participants) recipients = target.participants else diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index a6b68749a711328cb4bb12e8e9e87fec028959ff..44e494525b350bd5103e1adf22f75f7a47de8a79 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -18,7 +18,7 @@ class SystemHooksService def build_event_data(model, event) data = { event_name: build_event_name(model, event), - created_at: model.created_at + created_at: model.created_at.xmlschema } case model diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index b122b6c8658c62cc35507fd44cef8c1c23dd582d..29a55b36ca5a94657516c50d346ef1d67a322634 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -26,6 +26,10 @@ class AttachmentUploader < CarrierWave::Uploader::Base Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}" end + def url + Gitlab.config.gitlab.relative_url_root + super unless super.nil? + end + def file_storage? self.class.storage == CarrierWave::Storage::File end diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 9dcf7b488ee29397be6eedbe1d0b8bf1c30d9c80..8db2b2a709caaafb5805ebe281222608f5629b6b 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -25,7 +25,7 @@ - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/) - data = process.strip.split(' ') %tr - %td= Settings.gitlab.user + %td= gitlab_config.user - 5.times do %td= data.shift %td= data.join(' ') @@ -36,7 +36,7 @@ If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'. %p %i.fa.fa-exclamation-circle - If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{Settings.gitlab.user} -f sidekiq) and restart GitLab. + If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab. diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 2913b938027ac57d14a48ebd932979d900425bba..9426f87f998530305c9e317f1551b1b41b30469a 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -2,39 +2,20 @@ - if @group.errors.any? .alert.alert-danger %span= @group.errors.full_messages.first - .form-group.group_name_holder - = f.label :name, class: 'control-label' do - Group name - .col-sm-10 - = f.text_field :name, placeholder: "Example Group", class: "form-control" - .form-group.group-description-holder - = f.label :description, "Details", class: 'control-label' - .col-sm-10 - = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 + = render 'shared/group_form', f: f .form-group.group-description-holder = f.label :avatar, "Group avatar", class: 'control-label' .col-sm-10 - %a.choose-btn.btn.btn-small.js-choose-group-avatar-button - %i.fa.fa-paperclip - %span Choose File ... - - %span.file_name.js-avatar-filename File name... - = f.file_field :avatar, class: "js-group-avatar-input hidden" - .light The maximum file size allowed is 100KB. + = render 'shared/choose_group_avatar_button', f: f - if @group.new_record? .form-group .col-sm-2 .col-sm-10 .bs-callout.bs-callout-info - %ul - %li A group is a collection of several projects - %li Groups are private by default - %li Members of a group may only view projects they have permission to access - %li Group project URLs are prefixed with the group namespace - %li Existing projects may be moved into a group + = render 'shared/group_tips' .form-actions = f.submit 'Create group', class: "btn btn-create" = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel" diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 09105679bd2552103dd54307fde18cd1254869a7..1d7fef43184b5fabaeafda924f22a99f05b95e02 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -10,7 +10,7 @@ = form_tag admin_groups_path, method: :get, class: 'form-inline' do .form-group = text_field_tag :name, params[:name], class: "form-control input-mn-300" - = submit_tag "Search", class: "btn submit btn-primary" + = button_tag "Search", class: "btn submit btn-primary" %hr diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index ea62adb70ac59a9f8f617de23b5cc76ed9f9b9d6..1ff3d4061fd049bb8c89504ed1fbe7498409fa7a 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -91,7 +91,7 @@ %div.prepend-top-10 = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" %hr - = submit_tag 'Add users into group', class: "btn btn-create" + = button_tag 'Add users into group', class: "btn btn-create" .panel.panel-default .panel-heading %h3.panel-title diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index b3f8f012f000b7e8ef76963803582e2e21e71f37..384c6ee9af5e4fe063a5b32d23946d809fec79c1 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -1,68 +1,25 @@ +- loggers = [Gitlab::GitLogger, Gitlab::AppLogger, + Gitlab::ProductionLogger, Gitlab::SidekiqLogger] %ul.nav.nav-tabs.log-tabs - %li.active - = link_to "githost.log", "#githost", 'data-toggle' => 'tab' - %li - = link_to "application.log", "#application", 'data-toggle' => 'tab' - %li - = link_to "production.log", "#production", 'data-toggle' => 'tab' - %li - = link_to "sidekiq.log", "#sidekiq", 'data-toggle' => 'tab' - + - loggers.each do |klass| + %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } + = link_to klass::file_name, "##{klass::file_name_noext}", + 'data-toggle' => 'tab' %p.light To prevent performance issues admin logs output the last 2000 lines .tab-content - .tab-pane.active#githost - .file-holder#README - .file-title - %i.fa.fa-file - githost.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.fa.fa-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::GitLogger.read_latest.each do |line| - %li - %p= line - .tab-pane#application - .file-holder#README - .file-title - %i.fa.fa-file - application.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.fa.fa-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::AppLogger.read_latest.each do |line| - %li - %p= line - .tab-pane#production - .file-holder#README - .file-title - %i.fa.fa-file - production.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.fa.fa-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::Logger.read_latest_for('production.log').each do |line| - %li - %p= line - .tab-pane#sidekiq - .file-holder#README - .file-title - %i.fa.fa-file - sidekiq.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.fa.fa-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::Logger.read_latest_for('sidekiq.log').each do |line| - %li - %p= line + - loggers.each do |klass| + .tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''), + id: klass::file_name_noext } + .file-holder#README + .file-title + %i.fa.fa-file + = klass::file_name + .pull-right + = link_to '#', class: 'log-bottom' do + %i.fa.fa-arrow-down + Scroll down + .file-content.logs + %ol + - klass.read_latest.each do |line| + %li + %p= line diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 5ca6090f8d3a0f439fb50ae356a9d88f0ce0b633..2cd6b12be7f47c6fc0ee17a6c16e494702cabdb2 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -35,7 +35,7 @@ = label %hr = hidden_field_tag :sort, params[:sort] - = submit_tag "Search", class: "btn submit btn-primary" + = button_tag "Search", class: "btn submit btn-primary" = link_to "Reset", admin_projects_path, class: "btn btn-cancel" .col-md-9 diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 1326cc0aac9ef167c4d9eb4369569ed368644dc8..f6cbf9b82ba4edfb24486384e1173fb1d76feb12 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -6,8 +6,8 @@ .devise-errors = devise_error_messages! = f.hidden_field :reset_password_token - %div - = f.password_field :password, class: "form-control top", placeholder: "New password", required: true + .form-group#password-strength + = f.password_field :password, class: "form-control top", id: "user_password_recover", placeholder: "New password", required: true %div = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true .clearfix.append-bottom-10 diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index d6a952f3dc55245d017ba83d40780e1cd71ba373..123de881f594e914bbe23dbb51c08384a896773e 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -11,8 +11,8 @@ = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true %div = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true - %div - = f.password_field :password, class: "form-control middle", placeholder: "Password", required: true + .form-group#password-strength + = f.password_field :password, class: "form-control middle", id: "user_password_sign_up", placeholder: "Password", required: true %div = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true %div diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 01584611493a42e3c7ecdfba69b8dda794ceda7e..bf8a593c254e65753763ca6e034b2e7e3169da87 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -2,4 +2,4 @@ = text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} %br/ - = submit_tag "LDAP Sign in", class: "btn-save btn" + = button_tag "LDAP Sign in", class: "btn-save btn" diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index c8243ff782cecd194cc172cbe70c7cb925262acc..709d062df8351e15f0273d995aceb801dac1db75 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -4,7 +4,7 @@ .form-group = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search" .form-group - = submit_tag 'Search', class: "btn btn-primary wide" + = button_tag 'Search', class: "btn btn-primary wide" .pull-right .dropdown.inline diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index c8bf78385e80775c5db8b0289046e339794603fe..f797c4e3830d0250f57fdae04c391c1c2743371e 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -4,7 +4,7 @@ .form-group = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search" .form-group - = submit_tag 'Search', class: "btn btn-primary wide" + = button_tag 'Search', class: "btn btn-primary wide" .pull-right .dropdown.inline diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 93ebeeecdc85e9cead8cb04543cff46f4df29886..0eda33eeb50c2fc5ec6d98c007c014bbda4d3862 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -11,16 +11,8 @@ - if @group.errors.any? .alert.alert-danger %span= @group.errors.full_messages.first - .form-group - = f.label :name, class: 'control-label' do - Group name - .col-sm-10 - = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control left" + = render 'shared/group_form', f: f - .form-group.group-description-holder - = f.label :description, "Details", class: 'control-label' - .col-sm-10 - = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 .form-group .col-sm-2 .col-sm-10 @@ -30,13 +22,7 @@ You can change your group avatar here - else You can upload a group avatar here - %a.choose-btn.btn.btn-small.js-choose-group-avatar-button - %i.fa.fa-paperclip - %span Choose File ... - - %span.file_name.js-avatar-filename File name... - = f.file_field :avatar, class: "js-group-avatar-input hidden" - .light The maximum file size allowed is 100KB. + = render 'shared/choose_group_avatar_button', f: f - if @group.avatar? %hr = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" diff --git a/app/views/groups/members.html.haml b/app/views/groups/members.html.haml index a8ce8267d98f335ddd1948b0b3637047bda4c8ea..2e18a024e7bd43e1701e0b05dcfeb00bc93f56d1 100644 --- a/app/views/groups/members.html.haml +++ b/app/views/groups/members.html.haml @@ -13,7 +13,7 @@ = form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form' do .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' } - = submit_tag 'Search', class: 'btn' + = button_tag 'Search', class: 'btn' - if current_user && current_user.can?(:manage_group, @group) .pull-right diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 235e299343ad2634d850c864170e747eb9129dc7..6e17cdaef6f9c9a1870b74c592e2e359b976c607 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -2,37 +2,18 @@ - if @group.errors.any? .alert.alert-danger %span= @group.errors.full_messages.first - .form-group - = f.label :name, class: 'control-label' do - Group name - .col-sm-10 - = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control", tabindex: 1, autofocus: true - .form-group.group-description-holder - = f.label :description, "Details", class: 'control-label' - .col-sm-10 - = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4, tabindex: 2 + = render 'shared/group_form', f: f, autofocus: true .form-group.group-description-holder = f.label :avatar, "Group avatar", class: 'control-label' .col-sm-10 - %a.choose-btn.btn.btn-small.js-choose-group-avatar-button - %i.fa.fa-paperclip - %span Choose File ... - - %span.file_name.js-avatar-filename File name... - = f.file_field :avatar, class: "js-group-avatar-input hidden" - .light The maximum file size allowed is 100KB. + = render 'shared/choose_group_avatar_button', f: f .form-group .col-sm-2 .col-sm-10 - %ul - %li A group is a collection of several projects - %li Groups are private by default - %li Members of a group may only view projects they have permission to access - %li Group project URLs are prefixed with the group namespace - %li Existing projects may be moved into a group + = render 'shared/group_tips' .form-actions = f.submit 'Create group', class: "btn btn-create", tabindex: 3 diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 5ab82122ad762a8e006661030b43e3b8271008dd..2460a6a014d9103bd045f158720602e0dd5c2597 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -8,7 +8,7 @@ - if @snippet || @snippets = hidden_field_tag :snippets, true = hidden_field_tag :repository_ref, @ref - = submit_tag 'Go' if ENV['RAILS_ENV'] == 'test' + = button_tag 'Go' if ENV['RAILS_ENV'] == 'test' .search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref } :javascript diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index ab421d63f1af85e445384102b15ff95890e3d2aa..da4519613273e5139686c28de0c7e569cdc81661 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -28,3 +28,4 @@ You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, project_url(@project)} project team. - if @target_url #{link_to "View it on GitLab", @target_url} + = email_action @target_url diff --git a/app/views/notify/new_ssh_key_email.html.haml b/app/views/notify/new_ssh_key_email.html.haml index deb0822d8f2ce889dc420b7719dd0b1eff318bd6..63b0cbbd2051a7deaf9bb7519848f382ed15621f 100644 --- a/app/views/notify/new_ssh_key_email.html.haml +++ b/app/views/notify/new_ssh_key_email.html.haml @@ -6,5 +6,5 @@ title: %code= @key.title %p - If this key was added in error, you can remove it here: + If this key was added in error, you can remove it under = link_to "SSH Keys", profile_keys_url diff --git a/app/views/notify/new_ssh_key_email.text.erb b/app/views/notify/new_ssh_key_email.text.erb index 5f0080c2b7619db393cb9e2e549e11abad89bf7b..05b551c89a02cddd9826da2ac0aed6129fd9b9d9 100644 --- a/app/views/notify/new_ssh_key_email.text.erb +++ b/app/views/notify/new_ssh_key_email.text.erb @@ -2,6 +2,6 @@ Hi <%= @user.name %>! A new public key was added to your account: -title.................. <%= @key.title %> +Title: <%= @key.title %> -If this key was added in error, you can remove it here: <%= profile_keys_url %> +If this key was added in error, you can remove it at <%= profile_keys_url %> diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 2a7d317aa3e00eb787b4bde7d00a9fbb3838b413..425200ff523120d225cc10bb11d6207a4b123092 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -24,7 +24,7 @@ .form-group = f.label :password, 'New password', class: 'control-label' .col-sm-10 - = f.password_field :password, required: true, class: 'form-control' + = f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile' .form-group = f.label :password_confirmation, class: 'control-label' .col-sm-10 diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index aef7348fd203a8053dcb241cc37af2b09b20b138..42d2d0db29c4eac0e9bde4327fa8aceecbf72083 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -16,7 +16,7 @@ .col-sm-10= f.password_field :current_password, required: true, class: 'form-control' .form-group = f.label :password, class: 'control-label' - .col-sm-10= f.password_field :password, required: true, class: 'form-control' + .col-sm-10= f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile' .form-group = f.label :password_confirmation, class: 'control-label' .col-sm-10 diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index 64c19a57803d162538f1852c6d56a4de94babdb0..812d88a873087ae886b8881642bea8475864c325 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -23,5 +23,6 @@ tree_join(@commit.sha, @path)), class: 'btn btn-small' - if allowed_tree_edit? - = link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do + = button_tag class: 'remove-blob btn btn-small btn-remove', + 'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do Remove diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml index da84dc4b6cf77d95704df39469441d82d4ce5beb..c5568315cb1002b8719fe9bf6373e888761e7a9d 100644 --- a/app/views/projects/blob/_remove.html.haml +++ b/app/views/projects/blob/_remove.html.haml @@ -15,7 +15,7 @@ .form-group .col-sm-2 .col-sm-10 - = submit_tag 'Remove file', class: 'btn btn-remove btn-remove-file' + = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" :javascript diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index f5a530d95f22655df34d97cf32a123a83dc4cc3f..a6623240da1e393195dae245916d3759f92e4ca8 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -15,7 +15,7 @@ .col-sm-10 = text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control' .form-actions - = submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3 + = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' :javascript diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index da6157cf1b615789e28868b91856a774c0b8c0ef..cb0a3747f7d091122d054fb3a27ff4f8ccec60a8 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -12,7 +12,7 @@ %span.input-group-addon to = text_field_tag :to, params[:to], class: "form-control" - = submit_tag "Compare", class: "btn btn-create commits-compare-btn" + = button_tag "Compare", class: "btn btn-create commits-compare-btn" - if compare_to_mr_button? = link_to compare_mr_path, class: 'prepend-left-10 btn' do %strong Make a merge request diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index c415ae2ddc8fa798700365edecc5d93116613218..bf7770ceedf5e0aca531f9ca42ee79d15f5cd189 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -10,7 +10,10 @@ - if @commit.parent_ids.present? = view_file_btn(@commit.parent_id, diff_file, project) - else - %span= diff_file.new_path + - if diff_file.renamed_file + %span= "#{diff_file.old_path} renamed to #{diff_file.new_path}" + - else + %span= diff_file.new_path - if diff_file.mode_changed? %span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index d60ba8505b82d72279d1253a313b2afe152311da..d28063694319ac55b40b06c819898d0a92675131 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -13,7 +13,7 @@ = f.label :name, class: 'control-label' do Project name .col-sm-10 - = f.text_field :name, placeholder: "Example Project", class: "form-control" + = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit" .form-group @@ -131,6 +131,12 @@ .errors-holder .panel-body = form_for(@project, html: { class: 'form-horizontal' }) do |f| + .form-group.project_name_holder + = f.label :name, class: 'control-label' do + Project name + .col-sm-9 + .form-group + = f.text_field :name, placeholder: "Example Project", class: "form-control" .form-group = f.label :path, class: 'control-label' do %span Path diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index b125706781c2d43d56a9fd18040bc09708683491..7525812696f73fe7edc4e66d539c91fac92fd78e 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -14,8 +14,6 @@ .issue-info - if issue.assignee assigned to #{link_to_member(@project, issue.assignee)} - - else - unassigned - if issue.votes_count > 0 = render 'votes/votes_inline', votable: issue - if issue.notes.any? diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml index 8c3f08233866f4a8a69dce0e3e4f7ea2d760796f..648f459dc9e975b2ca198016e14a8b628eb91ad8 100644 --- a/app/views/projects/issues/_issue_context.html.haml +++ b/app/views/projects/issues/_issue_context.html.haml @@ -19,6 +19,7 @@ = hidden_field_tag :issue_context = f.submit class: 'btn' - elsif issue.milestone - = link_to issue.milestone.title, project_milestone_path + = link_to project_milestone_path(@project, @issue.milestone) do + = @issue.milestone.title - else None diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 71eb0d5c86631c0020b32cb6df3997c6066522d9..aad58e48f6c3571c624dc3bd41aa518d5e844a1b 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -39,7 +39,7 @@ Open .creator - Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)} + Created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)} %h4.title = gfm escape_once(@issue.title) diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index 9540453ce3ebca1ca885c7989d13255b8dffa0de..63db4b30968c1fd23f8ad7e110df5ca79e6e9fdd 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -10,11 +10,11 @@ - target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path %p %strong Step 1. - Checkout the branch we are going to merge and pull in the code + Fetch the code and create a new branch pointing to it %pre.dark :preserve - git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} #{@merge_request.target_branch} - git pull #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch} + git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch} + git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD %p %strong Step 2. Merge the branch and push the changes to GitLab diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml index c47c0a3f64265815ef17acf46f6717e969d3f611..f09d3659774510cb2a5dd0bc90c0ab775e12b9b7 100644 --- a/app/views/projects/new_tree/show.html.haml +++ b/app/views/projects/new_tree/show.html.haml @@ -19,14 +19,14 @@ Encoding .col-sm-10 = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' - = render 'shared/commit_message_container', params: params, - placeholder: 'Add new file' .file-holder .file-title %i.fa.fa-file .file-content.code %pre#editor= params[:content] + = render 'shared/commit_message_container', params: params, + placeholder: 'Add new file' = hidden_field_tag 'content', '', id: 'file-content' = render 'projects/commit_button', ref: @ref, cancel_path: project_tree_path(@project, @id) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 814bf19970c87cb8c3a7e1c8d03571c8fbb3bd83..a25c5e207fb174960f962f24c9692b99acacbe62 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -59,7 +59,7 @@ - if note.attachment.image? = link_to note.attachment.secure_url, target: '_blank' do = image_tag note.attachment.secure_url, class: 'note-image-attach' - .attachment.pull-right + .attachment = link_to note.attachment.secure_url, target: "_blank" do %i.fa.fa-paperclip = note.attachment_identifier diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index 0a07f440dd3b44b840c3878e201e2be1a2824e92..834524420796f85c1fa334a6fec483b22416f22e 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -28,7 +28,7 @@ - @service.fields.each do |field| - name = field[:name] - - value = @service.send(name) + - value = @service.send(name) unless field[:type] == 'password' - type = field[:type] - placeholder = field[:placeholder] - choices = field[:choices] @@ -47,6 +47,8 @@ = f.password_field name, class: "form-control" - elsif type == 'select' = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } + - elsif type == 'password' + = f.password_field name, class: 'form-control' .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index bce105a033bf631a5135080619d7a43f92013228..f93c1b4211fd5c9693e2ff63c127b846dd9ec1e7 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -4,6 +4,9 @@ = link_to project_commits_path(@project, tag.name), class: "" do %i.fa.fa-tag = tag.name + - if tag.message.present? + + = tag.message .pull-right - if can? current_user, :download_code, @project = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small' diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index aa08b3977639e6054d7c4f50f48820bf102abbe7..ad7ff8d3db8c2b7f50bdd3b11c8f5b300a402c6c 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -21,7 +21,7 @@ = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control' .light (Optional) Entering a message will create an annotated tag. .form-actions - = submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3 + = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel' :javascript diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml index 5f29b58de32367915fc833b24d5f30336376ba95..7a9c0939ba04ebb560610bcec97e85c5860205f0 100644 --- a/app/views/projects/team_members/_team_member.html.haml +++ b/app/views/projects/team_members/_team_member.html.haml @@ -5,7 +5,7 @@ - unless @project.personal? && user == current_user .pull-left = form_for(member, as: :project_member, url: project_team_member_path(@project, member.user)) do |f| - = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "medium project-access-select span2 trigger-submit" + = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "trigger-submit" = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do %i.fa.fa-minus.fa-inverse diff --git a/app/views/projects/team_members/import.html.haml b/app/views/projects/team_members/import.html.haml index 510b579fe2f878025ffc410211d39342684e6539..d1f46c61b2e7d869fcce287a8fce9cf3ab3d6610 100644 --- a/app/views/projects/team_members/import.html.haml +++ b/app/views/projects/team_members/import.html.haml @@ -9,6 +9,6 @@ .col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true) .form-actions - = submit_tag 'Import project members', class: "btn btn-create" + = button_tag 'Import project members', class: "btn btn-create" = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel" diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index bae57917a4c26d129ecda1d76e74ef7566432067..5b4816e4c40d38129309d3b7abca456065d2b95e 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -6,7 +6,7 @@ .col-sm-6 = search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search" .col-sm-4 - = submit_tag 'Search', class: "btn btn-create" + = button_tag 'Search', class: "btn btn-create" .form-group .col-sm-2 - unless params[:snippets].eql? 'true' diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..f32c2d388a7b6446c07437665127ad01e5fbf99a --- /dev/null +++ b/app/views/shared/_choose_group_avatar_button.html.haml @@ -0,0 +1,7 @@ +%a.choose-btn.btn.btn-small.js-choose-group-avatar-button + %i.fa.fa-paperclip + %span Choose File ... + +%span.file_name.js-avatar-filename File name... += f.file_field :avatar, class: 'js-group-avatar-input hidden' +.light The maximum file size allowed is 100KB. diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..93294e4250541d4e4fd13e4873b69b33424bd794 --- /dev/null +++ b/app/views/shared/_group_form.html.haml @@ -0,0 +1,12 @@ +.form-group + = f.label :name, class: 'control-label' do + Group name + .col-sm-10 + = f.text_field :name, placeholder: 'Example Group', class: 'form-control', + autofocus: local_assigns[:autofocus] || false + +.form-group.group-description-holder + = f.label :description, 'Details', class: 'control-label' + .col-sm-10 + = f.text_area :description, maxlength: 250, + class: 'form-control js-gfm-input', rows: 4 diff --git a/app/views/shared/_group_tips.html.haml b/app/views/shared/_group_tips.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..e5cf783beb7ae21505440dfc6296c159bd382a74 --- /dev/null +++ b/app/views/shared/_group_tips.html.haml @@ -0,0 +1,6 @@ +%ul + %li A group is a collection of several projects + %li Groups are private by default + %li Members of a group may only view projects they have permission to access + %li Group project URLs are prefixed with the group namespace + %li Existing projects may be moved into a group diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml index 09b2985d49846caa34e8215e2879d1bc8edc2766..ea008c2dede73bc3a0a836c9557e57eb45ab5de7 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -1,3 +1,3 @@ - groups.each do |group| = link_to group, class: 'profile-groups-avatars', :title => group.name do - = image_tag group_icon(group.path) + - image_tag group_icon(group.path) diff --git a/config/application.rb b/config/application.rb index e36df913d0b3309620e692f18e6e623e2ba975f7..44a5d68d1268c67dd9538844f73f72e9acc7b9c7 100644 --- a/config/application.rb +++ b/config/application.rb @@ -13,7 +13,6 @@ module Gitlab # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths += %W(#{config.root}/lib - #{config.root}/app/finders #{config.root}/app/models/hooks #{config.root}/app/models/concerns #{config.root}/app/models/project_services @@ -23,10 +22,6 @@ module Gitlab # :all can be used as a placeholder for all plugins not explicitly named. # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. - # config.time_zone = 'Central Time (US & Canada)' - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 4e3b390bbeff33fc66d2ae5c29a4336ed0900c98..a765b88f038e968d2725746c673fbb168bd92486 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -33,7 +33,14 @@ production: &base # Uncomment and customize if you can't use the default user to run GitLab (default: 'git') # user: git + ## Date & Time settings + # Uncomment and customize if you want to change the default time zone of GitLab application. + # To see all available zones, run `bundle exec rake time:zones:all` + # time_zone: 'UTC' + ## Email settings + # Uncomment and set to false if you need to disable email sending from GitLab (default: true) + # email_enabled: true # Email address used in the "From" field in mails sent by GitLab email_from: example@example.com diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 466165e200917a15b9e401ff47038704e1a7e68d..838718d11828f0526b5a2023863d3da4194d1537 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -102,6 +102,7 @@ Settings.gitlab['https'] = false if Settings.gitlab['https'].nil? Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80 Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || '' Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" +Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil? Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}" Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['user'] ||= 'git' @@ -110,6 +111,7 @@ Settings.gitlab['user_home'] ||= begin rescue ArgumentError # no user configured '/home/' + Settings.gitlab['user'] end +Settings.gitlab['time_zone'] ||= nil Settings.gitlab['signup_enabled'] ||= false Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) diff --git a/config/initializers/disable_email_interceptor.rb b/config/initializers/disable_email_interceptor.rb new file mode 100644 index 0000000000000000000000000000000000000000..c76a6b8b19f51dbd1a6855eb171cc0b725e58863 --- /dev/null +++ b/config/initializers/disable_email_interceptor.rb @@ -0,0 +1,2 @@ +# Interceptor in lib/disable_email_interceptor.rb +ActionMailer::Base.register_interceptor(DisableEmailInterceptor) unless Gitlab.config.gitlab.email_enabled diff --git a/config/initializers/time_zone.rb b/config/initializers/time_zone.rb new file mode 100644 index 0000000000000000000000000000000000000000..ee246e67d6687ea88fe0345a852a3902fa2a16bd --- /dev/null +++ b/config/initializers/time_zone.rb @@ -0,0 +1 @@ +Time.zone = Gitlab.config.gitlab.time_zone || Time.zone diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example index 6833082d68b844884f23bde24c3d1d5542d883e3..ea22744fd90c49eb4817f0c0ec8952b30df4b7ee 100644 --- a/config/unicorn.rb.example +++ b/config/unicorn.rb.example @@ -15,6 +15,7 @@ # Use at least one worker per core if you're on a dedicated server, # more will usually help for _short_ waits on databases/caches. +# The minimum is 2 worker_processes 2 # Since Unicorn is never exposed to outside clients, it does not need to diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb index e0b13db020d508cde255b65760eb0216046e39ca..0755ac714e11c217c395bf71d5f31cb0d0a94c55 100644 --- a/db/fixtures/production/001_admin.rb +++ b/db/fixtures/production/001_admin.rb @@ -1,8 +1,10 @@ -password = if ENV['GITLAB_ROOT_PASSWORD'].blank? - "5iveL!fe" - else - ENV['GITLAB_ROOT_PASSWORD'] - end +if ENV['GITLAB_ROOT_PASSWORD'].blank? + password = '5iveL!fe' + expire_time = Time.now +else + password = ENV['GITLAB_ROOT_PASSWORD'] + expire_time = nil +end admin = User.create( email: "admin@example.com", @@ -10,7 +12,7 @@ admin = User.create( username: 'root', password: password, password_confirmation: password, - password_expires_at: Time.now, + password_expires_at: expire_time, theme_id: Gitlab::Theme::MARS ) diff --git a/db/migrate/20140907220153_serialize_service_properties.rb b/db/migrate/20140907220153_serialize_service_properties.rb index b95f5b82e03a320c2d5e439d63d54b39ce0ae84d..bd75ab1eacb606b0a6d420588cfc98ad42a88d50 100644 --- a/db/migrate/20140907220153_serialize_service_properties.rb +++ b/db/migrate/20140907220153_serialize_service_properties.rb @@ -23,7 +23,7 @@ class SerializeServiceProperties < ActiveRecord::Migration associations[service.type.to_sym].each do |attribute| service.send("#{attribute}=", service.attributes[attribute.to_s]) end - service.save! + service.save end remove_column :services, :project_url, :string diff --git a/db/migrate/20141006143943_move_slack_service_to_webhook.rb b/db/migrate/20141006143943_move_slack_service_to_webhook.rb index 4b62b223cbfbf65ba85dd2fcac541c1ad3194f64..a8e07033a5d2d1ecaeda211f2e859e7905a4c912 100644 --- a/db/migrate/20141006143943_move_slack_service_to_webhook.rb +++ b/db/migrate/20141006143943_move_slack_service_to_webhook.rb @@ -10,7 +10,7 @@ class MoveSlackServiceToWebhook < ActiveRecord::Migration slack_service.properties.delete('subdomain') # Room is configured on the Slack side slack_service.properties.delete('room') - slack_service.save! + slack_service.save end end end diff --git a/doc/README.md b/doc/README.md index 6f24d8dec217cf6fb70b3b7790855fd1507da583..74543496466154d6c2ccc1aaa51cb7f5c483475d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -5,6 +5,7 @@ - [API](api/README.md) Explore how you can access GitLab via a simple and powerful API. - [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system. - [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do. +- [Project Services](project_services/project_services.md) Explore how project services can integrate a project with external services, such as for CI. - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project. - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. @@ -15,6 +16,7 @@ - [Install](install/README.md) Requirements, directory structures and manual installation. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter. - [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier. +- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough. - [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [Update](update/README.md) Update guides to upgrade your installation. diff --git a/doc/api/README.md b/doc/api/README.md index f76a253083f1ee962ec85fe8a5526619327fda04..ffe250df3ff1e8ada04ce112ba1f26e121252976 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -21,13 +21,7 @@ ## Clients -- [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP -- [Laravel API Wrapper for GitLab CE](https://github.com/adamgoose/gitlab) - PHP / [Laravel](http://laravel.com) -- [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby -- [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python -- [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java -- [node-gitlab](https://github.com/moul/node-gitlab) - Node.js -- [NGitLab](https://github.com/Scooletz/NGitLab) - .NET +Find API Clients for GitLab [on our website](https://about.gitlab.com/applications/#api-clients). ## Introduction @@ -158,7 +152,7 @@ When an attribute is missing, you will get something like: HTTP/1.1 400 Bad Request Content-Type: application/json - + { "message":"400 (Bad request) \"title\" not given" } @@ -167,7 +161,7 @@ When a validation error occurs, error messages will be different. They will hold HTTP/1.1 400 Bad Request Content-Type: application/json - + { "message": { "bio": [ diff --git a/doc/api/branches.md b/doc/api/branches.md index 74386615545664a8f9a8f6837607b85e2a358159..319f0b47386c24053c098e1b41b143784fdb851d 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -211,3 +211,11 @@ Parameters: It return 200 if succeed, 404 if the branch to be deleted does not exist or 400 for other reasons. In case of an error, an explaining message is provided. + +Success response: + +```json +{ + "branch_name": "my-removed-branch" +} +``` diff --git a/doc/api/commits.md b/doc/api/commits.md index 9475ecbaa67a3138f9044e5eaf38df0580cfccd9..eb8d6a43592a0f2210317c59719fac951e9ca36a 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -93,3 +93,66 @@ Parameters: } ] ``` + +## Get the comments of a commit + +Get the comments of a commit in a project. + +``` +GET /projects/:id/repository/commits/:sha/comments +``` + +Parameters: + +- `id` (required) - The ID of a project +- `sha` (required) - The name of a repository branch or tag or if not given the default branch + +```json +[ + { + "note": "this code is really nice", + "author": { + "id": 11, + "username": "admin", + "email": "admin@local.host", + "name": "Administrator", + "state": "active", + "created_at": "2014-03-06T08:17:35.000Z" + } + } +] +``` + +## Post comment to commit + +Adds a comment to a commit. Optionally you can post comments on a specific line of a commit. Therefor both `path`, `line_new` and `line_old` are required. + +``` +POST /projects/:id/repository/commits/:sha/comments +``` + +Parameters: + +- `id` (required) - The ID of a project +- `sha` (required) - The name of a repository branch or tag or if not given the default branch +- `note` (required) - Text of comment +- `path` (optional) - The file path +- `line` (optional) - The line number +- `line_type` (optional) - The line type (new or old) + +```json +{ + "author": { + "id": 1, + "username": "admin", + "email": "admin@local.host", + "name": "Administrator", + "blocked": false, + "created_at": "2012-04-29T08:46:00Z" + }, + "note": "text1", + "path": "example.rb", + "line": 5, + "line_type": "new" +} +``` diff --git a/doc/api/projects.md b/doc/api/projects.md index f2873fa44adee7b974f0b103fecc7e9d08df817f..880946bd99265d21d4374a687322a00cf5f23239 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -186,6 +186,7 @@ Parameters: "target_id": 830, "target_type": "Issue", "author_id": 1, + "author_username": "john", "data": null, "target_title": "Public project search field" }, @@ -196,6 +197,7 @@ Parameters: "target_id": null, "target_type": null, "author_id": 1, + "author_username": "john", "data": { "before": "50d4420237a9de7be1304607147aec22e4a14af7", "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428", @@ -231,6 +233,7 @@ Parameters: "target_id": 840, "target_type": "Issue", "author_id": 1, + "author_username": "john", "data": null, "target_title": "Finish & merge Code search PR" } diff --git a/doc/api/repositories.md b/doc/api/repositories.md index a412f60c0d9769b9568f6a1abbfac519a1fb9d4b..8acf85d21c81a927470831f3df9257e630776fd6 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -56,6 +56,7 @@ Parameters: [ { "name": "v1.0.0", + "message": "Release 1.0.0", "commit": { "id": "2695effb5807a22ff3d138d593fd856244e155e7", "parents": [], @@ -67,10 +68,11 @@ Parameters: "committed_date": "2012-05-28T04:42:42-07:00", "committer_email": "jack@example.com" }, - "protected": false } ] ``` +The message will be `nil` when creating a lightweight tag otherwise +it will contain the annotation. It returns 200 if the operation succeed. In case of an error, 405 with an explaining error message is returned. diff --git a/doc/api/services.md b/doc/api/services.md new file mode 100644 index 0000000000000000000000000000000000000000..ab9f9c00c67ae9bd62b6f91aa2e09d23d3fe2e1a --- /dev/null +++ b/doc/api/services.md @@ -0,0 +1,46 @@ +# Services + +## GitLab CI + +### Edit GitLab CI service + +Set GitLab CI service for a project. + +``` +PUT /projects/:id/services/gitlab-ci +``` + +Parameters: + +- `token` (required) - CI project token +- `project_url` (required) - CI project url + +### Delete GitLab CI service + +Delete GitLab CI service settings for a project. + +``` +DELETE /projects/:id/services/gitlab-ci +``` + +## Hipchat + +### Edit Hipchat service + +Set Hipchat service for project. + +``` +PUT /projects/:id/services/hipchat +``` +Parameters: + +- `token` (required) - Hipchat token +- `room` (required) - Hipchat room name + +### Delete Hipchat service + +Delete Hipchat service for a project. + +``` +DELETE /projects/:id/services/hipchat +``` diff --git a/doc/api/users.md b/doc/api/users.md index 3fdd3a75e888d99e9f02cc085332b469795953c9..20e0d68977e97238dd3ccdc7115431f7a2500892 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -78,7 +78,8 @@ GET /users "is_admin": false, "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", "can_create_group": true, - "can_create_project": true + "can_create_project": true, + "projects_limit": 100 } ] ``` @@ -140,7 +141,8 @@ Parameters: "color_scheme_id": 2, "is_admin": false, "can_create_group": true, - "can_create_project": true + "can_create_project": true, + "projects_limit": 100 } ``` @@ -240,7 +242,8 @@ GET /user "color_scheme_id": 2, "is_admin": false, "can_create_group": true, - "can_create_project": true + "can_create_project": true, + "projects_limit": 100 } ``` diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md new file mode 100644 index 0000000000000000000000000000000000000000..00867ead80d3086c9bc229a4c6cdaade10618fbd --- /dev/null +++ b/doc/hooks/custom_hooks.md @@ -0,0 +1,41 @@ +# Custom Git Hooks + +**Note: Custom git hooks must be configured on the filesystem of the GitLab +server. Only GitLab server administrators will be able to complete these tasks. +Please explore webhooks as an option if you do not have filesystem access.** + +Git natively supports hooks that are executed on different actions. +Examples of server-side git hooks include pre-receive, post-receive, and update. +See +[Git SCM Server-Side Hooks](http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks) +for more information about each hook type. + +As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab +administrators can add custom git hooks to any GitLab project. + +## Setup + +Normally, git hooks are placed in the repository or project's `hooks` directory. +GitLab creates a symlink from each project's `hooks` directory to the +gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell +upgrades. As such, custom hooks are implemented a little differently. Behavior +is exactly the same once the hook is created, though. Follow these steps to +set up a custom hook. + +1. Pick a project that needs a custom git hook. +1. On the GitLab server, navigate to the project's repository directory. +For a manual install the path is usually +`/home/git/repositories/<group>/<project>.git`. For Omnibus installs the path is +usually `/var/opt/gitlab/git-data/repositories/<group>/<project>.git`. +1. Create a new directory in this location called `custom_hooks`. +1. Inside the new `custom_hooks` directory, create a file with a name matching +the hook type. For a pre-receive hook the file name should be `pre-receive` with +no extension. +1. Make the hook file executable and make sure it's owned by git. +1. Write the code to make the git hook function as expected. Hooks can be +in any language. Ensure the 'shebang' at the top properly reflects the language +type. For example, if the script is in Ruby the shebang will probably be +`#!/usr/bin/env ruby`. + +That's it! Assuming the hook code is properly implemented the hook will fire +as appropriate. diff --git a/doc/install/installation.md b/doc/install/installation.md index d92282493f675f932ae3c479b5ee124a0249bc09..29e813e9ac9e0dbfd4e0c0dacd58f771749efa64 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -76,6 +76,7 @@ Is the system packaged Git too old? Remove it and compile from source. cd /tmp curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz cd git-2.1.2/ + ./configure make prefix=/usr/local all # Install into /usr/local/bin @@ -91,7 +92,7 @@ Then select 'Internet Site' and press enter to confirm the hostname. ## 2. Ruby -The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system ruby. +The use of Ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby. Remove the old Ruby 1.8 if present @@ -126,7 +127,8 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Login to PostgreSQL sudo -u postgres psql -d template1 - # Create a user for GitLab. + # Create a user for GitLab + # Do not type the 'template1=#', this is part of the prompt template1=# CREATE USER git CREATEDB; # Create the GitLab production database & grant all privileges on database @@ -137,6 +139,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Try connecting to the new database with the new user sudo -u git -H psql -d gitlabhq_production + + # Quit the database session + gitlabhq_production> \q ## 5. Redis @@ -150,6 +155,17 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Enable Redis socket for default Debian / Ubuntu path echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf + # Grant permission to the socket to all members of the redis group + echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf + + # Create the directory which contains the socket + mkdir /var/run/redis + chown redis:redis /var/run/redis + chmod 755 /var/run/redis + # Persist the directory which contains the socket, if applicable + if [ -d /etc/tmpfiles.d ]; then + echo 'd /var/run/redis 0755 redis redis 10d -' | sudo tee -a /etc/tmpfiles.d/redis.conf + fi # Activate the changes to redis.conf sudo service redis-server restart @@ -185,7 +201,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Make sure GitLab can write to the log/ and tmp/ directories sudo chown -R git log/ sudo chown -R git tmp/ - sudo chmod -R u+rwX log/ + sudo chmod -R u+rwX,go-w log/ sudo chmod -R u+rwX tmp/ # Create directory for satellites diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 3e4c6a28c0e38744aec2df8aa7b10a30aeb167d2..fd59ac8a0734c71e32d765dba1fdb5dc3e677eae 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -24,7 +24,7 @@ For the installations options please see [the installation page on the GitLab we On the above unsupported distributions is still possible to install GitLab yourself. Please see the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information. -### Non Unix operating systems such as Windows +### Non-Unix operating systems such as Windows GitLab is developed for Unix operating systems. GitLab does **not** run on Windows and we have no plans of supporting it in the near future. @@ -50,11 +50,6 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab ### Memory -- 512MB is the absolute minimum but we do not recommend this amount of memory. -You will either need to configure 512MB or 1.5GB of swap space. -With 512MB of swap space you must configure only one unicorn worker. -With one unicorn worker only git over ssh access will work because the git over http access requires two running workers (one worker to receive the user request and one worker for the authorization check). -If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow http access but it will still be slow. - 1GB RAM + 1GB swap supports up to 100 users - **2GB RAM** is the **recommended** memory size and supports up to 500 users - 4GB RAM supports up to 2,000 users @@ -67,7 +62,7 @@ Notice: The 25 workers of Sidekiq will show up as separate processes in your pro ### Storage -The necessary hard drive space largely depends on the size of the repos you want to store in GitLab. But as a *rule of thumb* you should have at least twice as much free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. +The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them. @@ -90,7 +85,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o ## Supported web browsers - Chrome (Latest stable version) -- Firefox (Latest released version) +- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/)) - Safari 7+ (known problem: required fields in html5 do not work) - Opera (Latest released version) - IE 10+ diff --git a/doc/integration/gitlab_actions.png b/doc/integration/gitlab_actions.png new file mode 100644 index 0000000000000000000000000000000000000000..b08f54d137bd9ab1bde8471211f95016a9ee6aa0 Binary files /dev/null and b/doc/integration/gitlab_actions.png differ diff --git a/doc/integration/gitlab_buttons_in_gmail.md b/doc/integration/gitlab_buttons_in_gmail.md new file mode 100644 index 0000000000000000000000000000000000000000..5cfea5a90f88474741f6066d0076a42c00f51895 --- /dev/null +++ b/doc/integration/gitlab_buttons_in_gmail.md @@ -0,0 +1,11 @@ +# 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) + diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index 9f0a0b528191e8c97254fc4b7be2f42599effa21..38716036478a66e42eb11ee385df091d55fd7391 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -7,6 +7,95 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G GitLab user attributes such as nickname and email will be copied from the LDAP user entry. +## Configuring GitLab for LDAP integration + +To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`. +In GitLab Enterprise Edition you can have multiple LDAP servers connected to one GitLab server. + +Please note that before version 7.4, GitLab used a different syntax for configuring LDAP integration. +The old LDAP integration syntax still works in GitLab 7.4. +If your `gitlab.rb` or `gitlab.yml` file contains LDAP settings in both the old syntax and the new syntax, only the __old__ syntax will be used by GitLab. + +```ruby +# For omnibus packages +gitlab_rails['ldap_enabled'] = true +gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below +main: # 'main' is the GitLab 'provider ID' of this LDAP server + ## label + # + # A human-friendly name for your LDAP server. It is OK to change the label later, + # for instance if you find out it is too large to fit on the web page. + # + # Example: 'Paris' or 'Acme, Ltd.' + label: 'LDAP' + + host: '_your_ldap_server' + port: 636 + uid: 'sAMAccountName' + method: 'ssl' # "tls" or "ssl" or "plain" + bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' + password: '_the_password_of_the_bind_user' + + # This setting specifies if LDAP server is Active Directory LDAP server. + # For non AD servers it skips the AD specific queries. + # If your LDAP server is not AD, set this to false. + active_directory: true + + # If allow_username_or_email_login is enabled, GitLab will ignore everything + # after the first '@' in the LDAP username submitted by the user on login. + # + # Example: + # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials; + # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'. + # + # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to + # disable this setting, because the userPrincipalName contains an '@'. + allow_username_or_email_login: false + + # Base where we can search for users + # + # Ex. ou=People,dc=gitlab,dc=example + # + base: '' + + # Filter LDAP users + # + # Format: RFC 4515 http://tools.ietf.org/search/rfc4515 + # Ex. (employeeType=developer) + # + # Note: GitLab does not support omniauth-ldap's custom filter syntax. + # + user_filter: '' + +# GitLab EE only: add more LDAP servers +# Choose an ID made of a-z and 0-9 . This ID will be stored in the database +# so that GitLab can remember which LDAP server a user belongs to. +# uswest2: +# label: +# host: +# .... +EOS +``` + +If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`: + +``` +production: + # snip... + ldap: + enabled: false + servers: + main: # 'main' is the GitLab 'provider ID' of this LDAP server + ## label + # + # A human-friendly name for your LDAP server. It is OK to change the label later, + # for instance if you find out it is too large to fit on the web page. + # + # Example: 'Paris' or 'Acme, Ltd.' + label: 'LDAP' + # snip... +``` + ## Enabling LDAP sign-in for existing GitLab users When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user. @@ -117,15 +206,22 @@ If you want to limit all GitLab access to a subset of the LDAP users on your LDA The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515). ```ruby -# For omnibus-gitlab -gitlab_rails['ldap_user_filter'] = '(employeeType=developer)' +# For omnibus packages; new LDAP server syntax +gitlab_rails['ldap_servers'] = YAML.load <<-EOS +main: + # snip... + user_filter: '(employeeType=developer)' +EOS ``` ```yaml -# For installations from source +# For installations from source; new LDAP server syntax production: ldap: - user_filter: '(employeeType=developer)' + servers: + main: + # snip... + user_filter: '(employeeType=developer)' ``` Tip: if you want to limit access to the nested members of an Active Directory group you can use the following syntax: diff --git a/doc/integration/slack.md b/doc/integration/slack.md index 95cb0c6fae29e1102052018d5d4b15d72d855f31..f2e73f272ef6758bc1d69a04a61bf276eb0b2183 100644 --- a/doc/integration/slack.md +++ b/doc/integration/slack.md @@ -4,15 +4,23 @@ To enable Slack integration you must create an Incoming WebHooks integration on Slack; -1. Sign in to [Slack](https://slack.com) (https://YOURSUBDOMAIN.slack.com/services) -1. Click on the Integrations menu at the top of the page. -1. Add a new Integration. +1. [Sign in to Slack](https://slack.com/signin) + +1. Select **Configure Integrations** from the dropdown next to your team name. + +1. Select the **All Services** tab + +1. Click **Add** next to Incoming Webhooks + 1. Pick Incoming WebHooks -1. Choose the channel name you want to send notifications to, in the Settings section -1. Add Integrations. - - Optional step; You can change bot's name and avatar by clicking "change the name of your bot", and "change the icon" after that you have to click "Save settings". -Now, Slack is ready to get external hooks. Before you leave this page don't forget to get the Token that you'll need on GitLab. You can find it by clicking Expand button, located in the "Instructions for creating Incoming WebHooks" section. It's a random alpha-numeric text 24 characters long. +1. Choose the channel name you want to send notifications to + +1. Click **Add Incoming WebHooks Integration**Add Integrations. + - Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**. + +1. Copy the **Webhook URL**, we'll need this later for GitLab. + ## On GitLab @@ -26,10 +34,8 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this 1. Fill in your Slack details - - Mark as active it - - Type your subdomain's prefix (If your subdomain is https://somedomain.slack.com you only have to type the somedomain) - - Type in the token you got from Slack - - Type in the channel name you want to use (eg. #announcements) + - Mark it as active + - Paste in the webhook url you got from Slack Have fun :) diff --git a/doc/project_services/bamboo.md b/doc/project_services/bamboo.md new file mode 100644 index 0000000000000000000000000000000000000000..51668128c6245cf8222875a15cc2bb8084460ded --- /dev/null +++ b/doc/project_services/bamboo.md @@ -0,0 +1,60 @@ +# Atlassian Bamboo CI Service + +GitLab provides integration with Atlassian Bamboo for continuous integration. +When configured, pushes to a project will trigger a build in Bamboo automatically. +Merge requests will also display CI status showing whether the build is pending, +failed, or completed successfully. It also provides a link to the Bamboo build +page for more information. + +Bamboo doesn't quite provide the same features as a traditional build system when +it comes to accepting webhooks and commit data. There are a few things that +need to be configured in a Bamboo build plan before GitLab can integrate. + +## Setup + +### Complete these steps in Bamboo: + +1. Navigate to a Bamboo build plan and choose 'Configure plan' from the 'Actions' +dropdown. +1. Select the 'Triggers' tab. +1. Click 'Add trigger'. +1. Enter a description such as 'GitLab trigger' +1. Choose 'Repository triggers the build when changes are committed' +1. Check one or more repositories checkboxes +1. Enter the GitLab IP address in the 'Trigger IP addresses' box. This is a +whitelist of IP addresses that are allowed to trigger Bamboo builds. +1. Save the trigger. +1. In the left pane, select a build stage. If you have multiple build stages +you want to select the last stage that contains the git checkout task. +1. Select the 'Miscellaneous' tab. +1. Under 'Pattern Match Labelling' put '${bamboo.repository.revision.number}' +in the 'Labels' box. +1. Save + +Bamboo is now ready to accept triggers from GitLab. Next, set up the Bamboo +service in GitLab + +### Complete these steps in GitLab: + +1. Navigate to the project you want to configure to trigger builds. +1. Select 'Settings' in the top navigation. +1. Select 'Services' in the left navigation. +1. Click 'Atlassian Bamboo CI' +1. Select the 'Active' checkbox. +1. Enter the base URL of your Bamboo server. 'https://bamboo.example.com' +1. Enter the build key from your Bamboo build plan. Build keys are a short, +all capital letter, identifier that is unique. It will be something like PR-BLD +1. If necessary, enter username and password for a Bamboo user that has +access to trigger the build plan. Leave these fields blank if you do not require +authentication. +1. Save or optionally click 'Test Settings'. Please note that 'Test Settings' +will actually trigger a build in Bamboo. + +## Troubleshooting + +If builds are not triggered, these are a couple of things to keep in mind. + +1. Ensure you entered the right GitLab IP address in Bamboo under 'Trigger +IP addresses'. +1. Remember that GitLab only triggers builds on push events. A commit via the +web interface will not trigger CI currently. diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md new file mode 100644 index 0000000000000000000000000000000000000000..20a69a211dd664fa10c5f34b9f4df3637552531f --- /dev/null +++ b/doc/project_services/project_services.md @@ -0,0 +1,18 @@ +# Project Services + +__Project integrations with external services for continuous integration and more.__ + +## Services + +- Assemblia +- [Atlassian Bamboo CI](bamboo.md) An Atlassian product for continous integration. +- Build box +- Campfire +- Emails on push +- Flowdock +- Gemnasium +- GitLab CI +- Hipchat +- PivotalTracker +- Pushover +- Slack diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index 39b1a52a44d8aacc2c2a129e494e15bda79c1cbf..bb229e8acbbcb7e277e93674e7b7ca0d374b3de9 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -1,28 +1,45 @@ -# Import +# Import bare repositories into your GitLab instance -### Import bare repositories into GitLab project instance +## Notes -Notes: +- The owner of the project will be the first admin +- The groups will be created as needed +- The owner of the group will be the first admin +- Existing projects will be skipped -* project owner will be a first admin -* groups will be created as needed -* group owner will be the first admin -* existing projects will be skipped +## How to use -How to use: +### Create a new folder inside the git repositories path. This will be the name of the new group. -1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path) -2. run the command below +- For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` by default, unless you changed +it in the `/etc/gitlab/gitlab.rb` file. +- For manual installations, it is usually located at: `/home/git/repositories` or you can see where +your repositories are located by looking at `config/gitlab.yml` under the `gitlab_shell => repos_path` entry. +### Copy your bare repositories inside this newly created folder: + +``` +$ cp -r /old/git/foo.git/ /home/git/repositories/new_group/ +``` + +### Run the command below depending on your type of installation: + +#### Omnibus Installation + +``` +$ sudo gitlab-rake gitlab:import:repos ``` -# omnibus-gitlab -sudo gitlab-rake gitlab:import:repos -# installation from source or cookbook -bundle exec rake gitlab:import:repos RAILS_ENV=production +#### Manual Installation + +Before running this command you need to change the directory to where your GitLab installation is located: + +``` +$ cd /home/git/gitlab +$ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production ``` -Example output: +#### Example output ``` Processing abcd.git diff --git a/doc/release/monthly.md b/doc/release/monthly.md index c46a3ed9c930d29dce9e2eeffdc12ab69b179a70..affe634587d03ebc43f3473dfcbc758fdf192f8b 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -2,40 +2,42 @@ NOTE: This is a guide for GitLab developers. -# **15th - Code Freeze & Release Manager** +# **7 workdays before release - Code Freeze & Release Manager** -### **1. Stop merging in code, except for important bugfixes** +### **1. Stop merging in code, except for important bug fixes** ### **2. Release Manager** A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated. ### **3. Create an overall issue** -Name it "Release x.x.x" for easier searching. + +Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier searching. +Replace the dates with actual dates based on the number of workdays before the release. ``` -15th: +Xth: * Update the changelog (#LINK) * Triage the omnibus-gitlab milestone -16th: +Xth: * Merge CE in to EE (#LINK) * Close the omnibus-gitlab milestone -17th: +Xth: * Create x.x.0.rc1 (#LINK) * Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package) -18th: +Xth: * Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) * Regression issue and tweet about rc1 (#LINK) * Start blog post (#LINK) -21th: +Xth: * Do QA and fix anything coming out of it (#LINK) @@ -43,16 +45,13 @@ Name it "Release x.x.x" for easier searching. * Release CE and EE (#LINK) -23rd: - -* Prepare package for GitLab.com release (#LINK) +Xth: -24th: +* * Deploy to GitLab.com (#LINK) -* Deploy to GitLab.com (#LINK) ``` -### **4. Update Changelog** +### **4. Update changelog** Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing. @@ -60,26 +59,26 @@ Any changes not yet added to the changelog are added by lead developer and in th Ensure that there is enough time to incorporate the findings of the release candidate, etc. -# **16th - Merge the CE into EE** +# **6 workdays before release- Merge the CE into EE** Do this via a merge request. -# **17th - Create RC1** +# **5 workdays before release - Create RC1** The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub. ### **1. Update the installation guide** 1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay) -1. Check the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) -1. Check the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) +1. Check the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) +1. Check the [Git version](/lib/tasks/gitlab/check.rake#L794) 1. There might be other changes. Ask around. -### **2. Create an update guides** +### **2. Create update guides** -1. Create: CE update guide from previous version. Like `from-6-8-to-6.9` +1. Create: CE update guide from previous version. Like `7.3-to-7.4.md` 1. Create: CE to EE update guide in EE repository for latest version. -1. Update: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/6.0-to-6.x.md to latest version. +1. Update: `6.x-or-7.x-to-7.x.md` to latest version. It's best to copy paste the previous guide and make changes where necessary. The typical steps are listed below with any points you should specifically look at. @@ -98,9 +97,9 @@ List any major changes here, so the user is aware of them before starting to upg #### 3. Do users need to update dependencies like `git`? -- Check if the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) changed since the last release. +- Check if the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) changed since the last release. -- Check if the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) changed since the last release. +- Check if the [Git version](/lib/tasks/gitlab/check.rake#L794) changed since the last release. #### 4. Get latest code @@ -112,19 +111,19 @@ List any major changes here, so the user is aware of them before starting to upg Check if any of these changed since last release: -- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab> -- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab-ssl> +- [lib/support/nginx/gitlab](/lib/support/nginx/gitlab) +- [lib/support/nginx/gitlab-ssl](/lib/support/nginx/gitlab-ssl) - <https://gitlab.com/gitlab-org/gitlab-shell/commits/master/config.yml.example> -- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/gitlab.yml.example> -- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/unicorn.rb.example> -- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.mysql> -- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.postgresql> -- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/initializers/rack_attack.rb.example> -- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/resque.yml.example> +- [config/gitlab.yml.example](/config/gitlab.yml.example) +- [config/unicorn.rb.example](/config/unicorn.rb.example) +- [config/database.yml.mysql](/config/database.yml.mysql) +- [config/database.yml.postgresql](/config/database.yml.postgresql) +- [config/initializers/rack_attack.rb.example](/config/initializers/rack_attack.rb.example) +- [config/resque.yml.example](/config/resque.yml.example) #### 8. Need to update init script? -Check if the `init.d/gitlab` script changed since last release: <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/init.d/gitlab> +Check if the `init.d/gitlab` script changed since last release: [lib/support/init.d/gitlab](/lib/support/init.d/gitlab) #### 9. Start application @@ -144,38 +143,41 @@ Make sure the code quality indicators are green / good. - [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) -### **4. Set VERSION** +### **4. Run release tool** -Change version in VERSION to `x.x.0.rc1`. +**Make sure EE `master` has latest changes from CE `master`** -### **5. Tag** - -Create an annotated tag that points to the version change commit: +Get release tools ``` -git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1' +git clone git@dev.gitlab.org:gitlab/release-tools.git +cd release-tools ``` -### **6. Create stable branches** +Release candidate creates stable branch from master. +So we need to sync master branch between all CE remotes. Also do same for EE. -For GitLab EE, append `-ee` to the branch. +``` +bundle exec rake sync +``` -`x-x-stable-ee` +Create release candidate and stable branch: ``` -git checkout master -git pull -git checkout -b x-x-stable -git push <remote> x-x-stable +bundle exec rake release["x.x.0.rc1"] ``` Now developers can use master for merging new features. So you should use stable branch for future code chages related to release. -# **18th - Release RC1** +# **4 workdays before release - Release RC1** + +### **1. Determine QA person -### **1. Update GitLab.com** +Notify person of QA day. + +### **2. Update GitLab.com** Merge the RC1 EE code into GitLab.com. Once the build is green, create a package. @@ -183,18 +185,20 @@ If there are big database migrations consider testing them with the production d Try to deploy in the morning. It is important to do this as soon as possible, so we can catch any errors before we release the full version. -### **2. Prepare the blog post** +### **3. Prepare the blog post** - Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out. - Check the changelog of CE and EE for important changes. - Create a WIP MR for the blog post - Ask Dmitriy to add screenshots to the WIP MR. -- Decide with team who will be the MVP user. +- Decide with team who will be the MVP user. +- Create WIP MR for adding MVP to MVP page on website - Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible. +- Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master) - Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor) - After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments -### **3. Create a regressions issue** +### **4. Create a regressions issue** On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text: @@ -211,7 +215,7 @@ Tweet about the RC release: > GitLab x.x.0.rc1 is out. This release candidate is only suitable for testing. Please link regressions issues from LINK_TO_REGRESSION_ISSUE -# **21st - Preparation** +# **1 workdays before release - Preparation** ### **1. Pre QA merge** @@ -234,91 +238,64 @@ create an issue about it in order to discuss the next steps after the release. # **22nd - Release CE and EE** -For GitLab EE, append `-ee` to the branches and tags. - -`x-x-stable-ee` +**Make sure EE `x-x-stable-ee` has latest changes from CE `x-x-stable`** -`v.x.x.0-ee` -Note: Merge CE into EE if needed. +### **1. Release code** -### **1. Set VERSION to x.x.x and push** - -- Change the GITLAB_SHELL_VERSION file in `master` of the CE repository if the version changed. -- Change the GITLAB_SHELL_VERSION file in `master` of the EE repository if the version changed. -- Change the VERSION file in `master` branch of the CE repository and commit and push to origin. -- Change the VERSION file in `master` branch of the EE repository and commit and push to origin. - -### **2. Update installation.md** - -Update [installation.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) to the newest version in master. - -### **3. Push latest changes from x-x-stable branch to dev.gitlab.org** +Get release tools ``` -git checkout -b x-x-stable -git push origin x-x-stable +git clone git@dev.gitlab.org:gitlab/release-tools.git +cd release-tools ``` -### **4. Build the Omnibus packages** - -Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md). -This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase. - -### **5. Create annotated tag vx.x.x** - -In `x-x-stable` branch check for the SHA-1 of the commit with VERSION file changed. Tag that commit, +Bump version, create release tag and push to remotes: ``` -git tag -a vx.x.0 -m 'Version x.x.0' xxxxx +bundle exec rake release["x.x.0"] ``` -where `xxxxx` is SHA-1. -### **6. Push the tag and x-x-stable branch to the remotes** +### **2. Update installation.md** -For GitLab CE, push to dev, GitLab.com and GitHub. +Update [installation.md](/doc/install/installation.md) to the newest version in master. -For GitLab EE, push to the subscribers repo. -Make sure the branch is marked 'protected' on each of the remotes you pushed to. +### **3. Build the Omnibus packages** + +Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md). +This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase. -``` -git push <remote> x-x-stable(-ee) -git push <remote> vx.x.0 -``` -### **7. Publish packages for new release** +### **4. Publish packages for new release** Update `downloads/index.html` and `downloads/archive/index.html` in `www-gitlab-com` repository. -### **8. Publish blog for new release** +### **5. Publish blog for new release** Merge the [blog merge request](#1-prepare-the-blog-post) in `www-gitlab-com` repository. -### **9. Tweet to blog** +### **6. Tweet to blog** Send out a tweet to share the good news with the world. List the most important features and link to the blog post. Proposed tweet for CE "GitLab X.X is released! It brings *** <link-to-blogpost>" -### **10. Send out the newsletter** - -Send out an email to the 'GitLab Newsletter' mailing list on MailChimp. -Replicate the former release newsletter and modify it accordingly. -**Do not forget to edit `Subject line` and regenerate `Plain-Text Email` from HTML source** - -Include a link to the blog post and keep it short. +# **1 workday after release - Update GitLab.com** -Proposed email text: -"We have released a new version of GitLab. See our blog post(<link>) for more information." +Update GitLab.com from RC1 to the released package. +# **25th - Release GitLab CI** -# **23rd - Optional Patch Release** - -# **24th - Update GitLab.com** +- Create the update guid `doc/x.x-to-x.x.md`. +- Update CHANGELOG +- Bump version +- Create annotated tags `git tag -a vx.x.0 -m 'Version x.x.0' xxxxx` +- Create stable branch `x-x-stable` +- Create GitHub release post +- Post to blog about release +- Post to twitter -Merge the stable release into GitLab.com. Once the build is green deploy the next morning. -# **25th - Release GitLab CI** diff --git a/doc/release/patch.md b/doc/release/patch.md index bcc14568fc88d0f2583e4873a11336214efb35ee..6ed56427e9acaffc5d4a655effc0f54821556773 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -10,6 +10,8 @@ Otherwise include it in the monthly release and note there was a regression fix ## Release Procedure +### Preparation + 1. Verify that the issue can be reproduced 1. Note in the 'GitLab X.X regressions' that you will create a patch 1. Create an issue on private GitLab development server @@ -17,15 +19,36 @@ Otherwise include it in the monthly release and note there was a regression fix 1. Fix the issue on a feature branch, do this on the private GitLab development server 1. Consider creating and testing workarounds 1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch +1. Make sure that the build has passed and all tests are passing 1. In a separate commit in the stable branch update the CHANGELOG 1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X" -1. In a separate commit in the stable branch update the VERSION -1. Create an annotated tag vX.X.X for CE and another patch release for EE `git tag -a vx.x.x -m 'Version x.x.x'` -1. Make sure that the build has passed and all tests are passing -1. Push the code and the tags to all the CE and EE repositories + +### Bump version + +Get release tools + +``` +git clone git@dev.gitlab.org:gitlab/release-tools.git +cd release-tools +``` + +Bump version in stable branch, create release tag and push to remotes: + +``` +bundle exec rake release["x.x.x"] +``` + +Or if you need to release only EE: + +``` +CE=false be rake release['x.x.x'] +``` + +### Release + 1. Apply the patch to GitLab Cloud and the private GitLab development server 1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md) 1. Cherry-pick the changelog update back into master -1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing as well as the link to the changelog +1. Create and publish a blog post +1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing and link to the blog post 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only) -1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the patch is EE only) diff --git a/doc/update/6.x-or-7.x-to-7.4.md b/doc/update/6.x-or-7.x-to-7.4.md index 2fa6889af73f42cb423fd73e2f08c812374d83dc..dd90ae3bf3d6254d13b9b943bab809ae8d5f17b1 100644 --- a/doc/update/6.x-or-7.x-to-7.4.md +++ b/doc/update/6.x-or-7.x-to-7.4.md @@ -267,6 +267,7 @@ mysql> \q # Set production -> username: git # Set production -> password: the password your replaced $password with earlier sudo -u git -H editor /home/git/gitlab/config/database.yml +``` ## Things went south? Revert to previous version (6.0) diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md index 44f3f8f1a389cd62cdfd91b5b19916e8fce0af75..ebdd4ff60fa3dc5bf0933850d1a50ebfb07228af 100644 --- a/doc/update/7.2-to-7.3.md +++ b/doc/update/7.2-to-7.3.md @@ -74,7 +74,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab # Enable Redis socket for default Debian / Ubuntu path echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0). - sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf + sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf # Activate the changes to redis.conf sudo service redis-server restart # Add git to the redis group diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index 69d86fb06ede5eaf21b6ed65a42c39bc331c607b..3f471500c82745b32450c085446e75ef1a24c558 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -9,6 +9,7 @@ ```bash cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` ### 2. Get latest code diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index c4a77d128005cd0c04ea25ede9b5737f2305bf37..629c46ad0308c1bddfd3821953ccfc74e0d19a57 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -26,16 +26,14 @@ sudo -u git -H git checkout LATEST_TAG Replace LATEST_TAG with the latest GitLab tag you want to upgrade to, for example `v6.6.3`. -### 3. Update gitlab-shell if it is not the latest version +### 3. Update gitlab-shell to the corresponding version ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout LATEST_TAG +sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` ``` -Replace LATEST_TAG with the latest GitLab Shell tag you want to upgrade to, for example `v1.7.9`. - ### 4. Install libs, migrations, etc. ```bash diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md index cf59b0e461c5e455a7cc4d9f2014ad67c8a6bffb..44e18a9ed4237c626d3af8f170b4f8a43f0646dd 100644 --- a/doc/update/upgrader.md +++ b/doc/update/upgrader.md @@ -43,28 +43,31 @@ Check if GitLab and its dependencies are configured correctly: If all items are green, then congratulations upgrade is complete! -## 5. Upgrade GitLab Shell (if needed) +## 5. Upgrade GitLab Shell -If the `gitlab:check` task reports an outdated version of `gitlab-shell` you should upgrade it. - -Upgrade it by running the commands below after replacing 2.0.1 with the correct version number: +GitLab Shell might be outdated, running the commands below ensures you're using a compatible version: ``` cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v2.0.1 +sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` ``` ## One line upgrade command You've read through the entire guide and probably already did all the steps one by one. -Here is a one line command with step 1 to 4 for the next time you upgrade: +Here is a one line command with step 1 to 5 for the next time you upgrade: ```bash -cd /home/git/gitlab; sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \ +cd /home/git/gitlab; \ + sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \ sudo service gitlab stop; \ if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi; \ + cd /home/git/gitlab-shell; \ + sudo -u git -H git fetch; \ + sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \ + cd /home/git/gitlab; \ sudo service gitlab start; \ sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production ``` diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index 31791da80745ea2782b6759db608ac315c28d616..f19517c0f18a5ab1925264ff2b72e396a4a821e7 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -63,6 +63,11 @@ Triggered when a new issue is created or an existing issue was updated/closed/re ```json { "object_kind": "issue", + "user": { + "name": "Administrator", + "username": "root", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + }, "object_attributes": { "id": 301, "title": "New API: create/update/delete file", @@ -92,6 +97,11 @@ Triggered when a new merge request is created or an existing merge request was u ```json { "object_kind": "merge_request", + "user": { + "name": "Administrator", + "username": "root", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + }, "object_attributes": { "id": 99, "target_branch": "master", diff --git a/features/profile/profile.feature b/features/profile/profile.feature index d2125e013bc344daecb20541b16dbc577ca16cb4..d7fa370fe2a81b52d4677c9995dbc67a8e75ac2e 100644 --- a/features/profile/profile.feature +++ b/features/profile/profile.feature @@ -83,3 +83,22 @@ Feature: Profile Given I visit profile design page When I change my code preview theme Then I should receive feedback that the changes were saved + + @javascript + Scenario: I see the password strength indicator + Given I visit profile password page + When I try to set a weak password + Then I should see the input field yellow + + @javascript + Scenario: I see the password strength indicator error + Given I visit profile password page + When I try to set a short password + Then I should see the input field red + And I should see the password error message + + @javascript + Scenario: I see the password strength indicator with success + Given I visit profile password page + When I try to set a strong password + Then I should see the input field green \ No newline at end of file diff --git a/features/project/service.feature b/features/project/service.feature index 3a07f1dd990f78ff83f479f885ff6c7bf4b34ae8..493fbbcae5a36eea331f9feef82b1bf108fc3be8 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -60,3 +60,9 @@ Feature: Project Services And I click jira service link And I fill jira settings Then I should see jira service settings saved + + Scenario: Activate Atlassian Bamboo CI service + When I visit project "Shop" services page + And I click Atlassian Bamboo CI service link + And I fill Atlassian Bamboo CI settings + Then I should see Atlassian Bamboo CI service settings saved diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb index 332bfa95d97c0fb388cb3d09664c09cc0c4f8a72..3da3d62d0c0468efd690c33513ef0fb9829034ac 100644 --- a/features/steps/dashboard/event_filters.rb +++ b/features/steps/dashboard/event_filters.rb @@ -29,7 +29,7 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps step 'this project has push event' do data = { - before: "0000000000000000000000000000000000000000", + before: Gitlab::Git::BLANK_SHA, after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", ref: "refs/heads/new_design", user_id: @user.id, diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb index 6b5f88e5895b4d2a20bffcb52aca2a0ba178baa0..2a5850d091b05fe7d9c8ca1033f3c3d8164ae4d9 100644 --- a/features/steps/dashboard/issues.rb +++ b/features/steps/dashboard/issues.rb @@ -10,6 +10,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps step 'I should see issues authored by me' do should_see(authored_issue) + should_see(authored_issue_on_public_project) should_not_see(assigned_issue) should_not_see(other_issue) end @@ -22,6 +23,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps step 'I have authored issues' do authored_issue + authored_issue_on_public_project end step 'I have assigned issues' do @@ -64,6 +66,10 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps @other_issue ||= create :issue, project: project end + def authored_issue_on_public_project + @authored_issue_on_public_project ||= create :issue, author: current_user, project: public_project + end + def project @project ||= begin project =create :project @@ -71,4 +77,8 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps project end end + + def public_project + @public_project ||= create :project, :public + end end diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb index 95c378fa20128903b65a432a347472ac8f5afbed..75e53173d3f2cdad71ec00b7a560e20b369f9d5e 100644 --- a/features/steps/dashboard/merge_requests.rb +++ b/features/steps/dashboard/merge_requests.rb @@ -4,13 +4,17 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps step 'I should see merge requests assigned to me' do should_see(assigned_merge_request) + should_see(assigned_merge_request_from_fork) should_not_see(authored_merge_request) + should_not_see(authored_merge_request_from_fork) should_not_see(other_merge_request) end step 'I should see merge requests authored by me' do should_see(authored_merge_request) + should_see(authored_merge_request_from_fork) should_not_see(assigned_merge_request) + should_not_see(assigned_merge_request_from_fork) should_not_see(other_merge_request) end @@ -22,10 +26,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps step 'I have authored merge requests' do authored_merge_request + authored_merge_request_from_fork end step 'I have assigned merge requests' do assigned_merge_request + assigned_merge_request_from_fork end step 'I have other merge requests' do @@ -53,15 +59,41 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps end def assigned_merge_request - @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project + @assigned_merge_request ||= create :merge_request, + assignee: current_user, + target_project: project, + source_project: project end def authored_merge_request - @authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project + @authored_merge_request ||= create :merge_request, + source_branch: 'simple_merge_request', + author: current_user, + target_project: project, + source_project: project end def other_merge_request - @other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project + @other_merge_request ||= create :merge_request, + source_branch: '2_3_notes_fix', + target_project: project, + source_project: project + end + + def authored_merge_request_from_fork + @authored_merge_request_from_fork ||= create :merge_request, + source_branch: 'basic_page', + author: current_user, + target_project: public_project, + source_project: forked_project + end + + def assigned_merge_request_from_fork + @assigned_merge_request_from_fork ||= create :merge_request, + source_branch: 'basic_page_fix', + assignee: current_user, + target_project: public_project, + source_project: forked_project end def project @@ -71,4 +103,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps project end end + + def public_project + @public_project ||= create :project, :public + end + + def forked_project + @forked_project ||= Projects::ForkService.new(public_project, current_user).execute + end end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index adfaefb164499da47c15debe2dac271528f8d064..6d747b65bae29bac72c8a758dde317210d7fe02b 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -58,16 +58,34 @@ class Spinach::Features::Profile < Spinach::FeatureSteps step 'I try change my password w/o old one' do within '.update-password' do - fill_in "user_password", with: "22233344" + fill_in "user_password_profile", with: "22233344" fill_in "user_password_confirmation", with: "22233344" click_button "Save" end end + step 'I try to set a weak password' do + within '.update-password' do + fill_in "user_password_profile", with: "22233344" + end + end + + step 'I try to set a short password' do + within '.update-password' do + fill_in "user_password_profile", with: "short" + end + end + + step 'I try to set a strong password' do + within '.update-password' do + fill_in "user_password_profile", with: "Itulvo9z8uud%$" + end + end + step 'I change my password' do within '.update-password' do fill_in "user_current_password", with: "12345678" - fill_in "user_password", with: "22233344" + fill_in "user_password_profile", with: "22233344" fill_in "user_password_confirmation", with: "22233344" click_button "Save" end @@ -76,7 +94,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps step 'I unsuccessfully change my password' do within '.update-password' do fill_in "user_current_password", with: "12345678" - fill_in "user_password", with: "password" + fill_in "user_password_profile", with: "password" fill_in "user_password_confirmation", with: "confirmation" click_button "Save" end @@ -86,6 +104,22 @@ class Spinach::Features::Profile < Spinach::FeatureSteps page.should have_content "You must provide a valid current password" end + step 'I should see the input field yellow' do + page.should have_css 'div.has-warning' + end + + step 'I should see the input field green' do + page.should have_css 'div.has-success' + end + + step 'I should see the input field red' do + page.should have_css 'div.has-error' + end + + step 'I should see the password error message' do + page.should have_content 'Your password is too short' + end + step "I should see a password error message" do page.should have_content "Password confirmation doesn't match" end @@ -146,7 +180,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps step 'I submit new password' do fill_in :user_current_password, with: '12345678' - fill_in :user_password, with: '12345678' + fill_in :user_password_profile, with: '12345678' fill_in :user_password_confirmation, with: '12345678' click_button "Set new password" end diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 126a1a6b68c77e4db76999eff8ec272b306c61c4..2e2da185b9abc4b5d9ffb444dcc5eb0a76429183 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -4,7 +4,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps include SharedPaths step 'change project settings' do - fill_in 'project_name', with: 'NewName' + fill_in 'project_name_edit', with: 'NewName' uncheck 'project_issues_enabled' end diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index 8aebc58c4d14e06a1a4d1cf9c72cf7871b51f94a..59e30c56032ce06fceab35bca85ff1bb2c9edc16 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -14,6 +14,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps page.should have_content 'GitLab CI' page.should have_content 'Assembla' page.should have_content 'Pushover' + page.should have_content 'Atlassian Bamboo' end step 'I click gitlab-ci service link' do @@ -108,12 +109,12 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps step 'I fill Slack settings' do check 'Active' - fill_in 'Webhook', with: 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' + fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' click_button 'Save' end step 'I should see Slack service settings saved' do - find_field('Webhook').value.should == 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' + find_field('Webhook').value.should == 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' end step 'I click Pushover service link' do @@ -156,4 +157,23 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps find_field('Password').value.should_not == 'gitlab' find_field('Api version').value.should == '2' end + + step 'I click Atlassian Bamboo CI service link' do + click_link 'Atlassian Bamboo CI' + end + + step 'I fill Atlassian Bamboo CI settings' do + check 'Active' + fill_in 'Bamboo url', with: 'http://bamboo.example.com' + fill_in 'Build key', with: 'KEY' + fill_in 'Username', with: 'user' + fill_in 'Password', with: 'verySecret' + click_button 'Save' + end + + step 'I should see Atlassian Bamboo CI service settings saved' do + find_field('Bamboo url').value.should == 'http://bamboo.example.com' + find_field('Build key').value.should == 'KEY' + find_field('Username').value.should == 'user' + end end diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 665f5d6d1950c1aafe1cc9b99ece618b87b3aff9..ddd501d4f88288159e9687794327715465d7f5f3 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -78,7 +78,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I click on "Remove"' do - click_link 'Remove' + click_button 'Remove' end step 'I click on "Remove file"' do diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 81fe49a52fc82e27dabbf9f81eaf8173fc7cff57..0dff913b0b75593f828f07dd9a4d1b0564b8a3c1 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -32,7 +32,7 @@ module SharedProject @project = Project.find_by(name: "Shop") data = { - before: "0000000000000000000000000000000000000000", + before: Gitlab::Git::BLANK_SHA, after: "6d394385cf567f80a8fd85055db1ab4c5295806f", ref: "refs/heads/fix", user_id: @user.id, diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 14f8b20f6b288f5dbb6d2dd25d5312e80516586d..6ec1a753a69da9ade3d860d7492690f12d23053a 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -82,6 +82,7 @@ module API authorize_push_project result = CreateBranchService.new(user_project, current_user). execute(params[:branch_name], params[:ref]) + if result[:status] == :success present result[:branch], with: Entities::RepoObject, @@ -104,7 +105,9 @@ module API execute(params[:branch]) if result[:status] == :success - true + { + branch_name: params[:branch] + } else render_api_error!(result[:message], result[:return_code]) end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 4a67313430a07965998c79bdb2c4b39cd95ef706..6c5391b98c820dff50a4931bcbf2c03c4e9e2bf3 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -50,6 +50,67 @@ module API not_found! "Commit" unless commit commit.diffs end + + # Get a commit's comments + # + # Parameters: + # id (required) - The ID of a project + # sha (required) - The commit hash + # Examples: + # GET /projects/:id/repository/commits/:sha/comments + get ':id/repository/commits/:sha/comments' do + sha = params[:sha] + commit = user_project.repository.commit(sha) + not_found! 'Commit' unless commit + notes = Note.where(commit_id: commit.id) + present paginate(notes), with: Entities::CommitNote + end + + # Post comment to commit + # + # Parameters: + # id (required) - The ID of a project + # sha (required) - The commit hash + # note (required) - Text of comment + # path (optional) - The file path + # line (optional) - The line number + # line_type (optional) - The type of line (new or old) + # Examples: + # POST /projects/:id/repository/commits/:sha/comments + post ':id/repository/commits/:sha/comments' do + required_attributes! [:note] + + sha = params[:sha] + commit = user_project.repository.commit(sha) + not_found! 'Commit' unless commit + opts = { + note: params[:note], + noteable_type: 'Commit', + commit_id: commit.id + } + + if params[:path] && params[:line] && params[:line_type] + commit.diffs.each do |diff| + next unless diff.new_path == params[:path] + lines = Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a) + + lines.each do |line| + next unless line.new_pos == params[:line].to_i && line.type == params[:line_type] + break opts[:line_code] = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos) + end + + break if opts[:line_code] + end + end + + note = ::Notes::CreateService.new(user_project, current_user, opts).execute + + if note.save + present note, with: Entities::CommitNote + else + not_found! + end + end end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index ce3212eda99de596635df7cea615ea366650e32d..2f801cbeb3a62f104721a8256729ae2580797fe9 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -16,7 +16,8 @@ module API class UserFull < User expose :email - expose :theme_id, :color_scheme_id, :extern_uid, :provider + expose :theme_id, :color_scheme_id, :extern_uid, :provider, \ + :projects_limit expose :can_create_group?, as: :can_create_group expose :can_create_project?, as: :can_create_project end @@ -83,6 +84,25 @@ module API end end + class RepoTag < Grape::Entity + expose :name + expose :message do |repo_obj, _options| + if repo_obj.respond_to?(:message) + repo_obj.message + else + nil + end + end + + expose :commit do |repo_obj, options| + if repo_obj.respond_to?(:commit) + repo_obj.commit + elsif options[:project] + options[:project].repository.commit(repo_obj.target) + end + end + end + class RepoObject < Grape::Entity expose :name @@ -169,11 +189,25 @@ module API expose :author, using: Entities::UserBasic end + class CommitNote < Grape::Entity + expose :note + expose(:path) { |note| note.diff_file_name } + expose(:line) { |note| note.diff_new_line } + expose(:line_type) { |note| note.diff_line_type } + expose :author, using: Entities::UserBasic + end + class Event < Grape::Entity expose :title, :project_id, :action_name expose :target_id, :target_type, :author_id expose :data, :target_title expose :created_at + + expose :author_username do |event, options| + if event.author + event.author.username + end + end end class LdapGroup < Grape::Entity diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 1d54c2739bb607b02677724a26a045ea80f22317..2da4e68c4cb8c7878589a811ec8c5a11626dc5a6 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -178,7 +178,7 @@ module API # DELETE /projects/:id delete ":id" do authorize! :remove_project, user_project - user_project.destroy + ::Projects::DestroyService.new(user_project, current_user, {}).execute end # Mark this project as forked from another diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 626d99c26491201f4b6712848726fae6ea248128..a1a7721b28894cd542d87728fa90bafd4eb0dfd6 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -23,7 +23,8 @@ module API # Example Request: # GET /projects/:id/repository/tags get ":id/repository/tags" do - present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project + present user_project.repo.tags.sort_by(&:name).reverse, + with: Entities::RepoTag, project: user_project end # Create tag @@ -43,7 +44,7 @@ module API if result[:status] == :success present result[:tag], - with: Entities::RepoObject, + with: Entities::RepoTag, project: user_project else render_api_error!(result[:message], 400) diff --git a/lib/api/services.rb b/lib/api/services.rb index bde502e32e15825dd382e1468e3405dae76e42fb..3ad59cf3adf6ab95629b8fed1aa175f26efafeca 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -28,7 +28,7 @@ module API # Delete GitLab CI service settings # # Example Request: - # DELETE /projects/:id/keys/:id + # DELETE /projects/:id/services/gitlab-ci delete ":id/services/gitlab-ci" do if user_project.gitlab_ci_service user_project.gitlab_ci_service.update_attributes( @@ -38,7 +38,41 @@ module API ) end end + + # Set Hipchat service for project + # + # Parameters: + # token (required) - Hipchat token + # room (required) - Hipchat room name + # + # Example Request: + # PUT /projects/:id/services/hipchat + put ':id/services/hipchat' do + required_attributes! [:token, :room] + attrs = attributes_for_keys [:token, :room] + user_project.build_missing_services + + if user_project.hipchat_service.update_attributes( + attrs.merge(active: true)) + true + else + not_found! + end + end + + # Delete Hipchat service settings + # + # Example Request: + # DELETE /projects/:id/services/hipchat + delete ':id/services/hipchat' do + if user_project.hipchat_service + user_project.hipchat_service.update_attributes( + active: false, + token: nil, + room: nil + ) + end + end end end end - diff --git a/lib/backup/database.rb b/lib/backup/database.rb index d12d30a91103ad4dde9c49e9d28352a6432b19bd..ea659e3b605fd81cb72fecb9395ff7b2f91efc64 100644 --- a/lib/backup/database.rb +++ b/lib/backup/database.rb @@ -34,6 +34,7 @@ module Backup # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE # statements like MySQL. Rake::Task["gitlab:db:drop_all_tables"].invoke + Rake::Task["gitlab:db:drop_all_postgres_sequences"].invoke pg_env system('psql', config['database'], '-f', db_file_name) end diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 380beac708dbe681e5db611ab617d9b03255a111..0bb02f1a357c70b58e3e8d0ab7dd8bf153fbcff2 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -91,7 +91,7 @@ module Backup protected def path_to_repo(project) - File.join(repos_path, project.path_with_namespace + '.git') + project.repository.path_to_repo end def path_to_bundle(project) diff --git a/lib/disable_email_interceptor.rb b/lib/disable_email_interceptor.rb new file mode 100644 index 0000000000000000000000000000000000000000..1b80be112a436dfad99e68a4b6a8a1ab6d5373ba --- /dev/null +++ b/lib/disable_email_interceptor.rb @@ -0,0 +1,8 @@ +# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails +class DisableEmailInterceptor + + def self.delivering_email(message) + message.perform_deliveries = false + Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}" + end +end diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb index 8e4717b46e6a0ab8b00b6b71884e3656d31ca324..dddcb2538f915a8eab9c2801522b62c511aa2c14 100644 --- a/lib/gitlab/app_logger.rb +++ b/lib/gitlab/app_logger.rb @@ -1,7 +1,7 @@ module Gitlab class AppLogger < Gitlab::Logger - def self.file_name - 'application.log' + def self.file_name_noext + 'application' end def format_message(severity, timestamp, progname, msg) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index ae33c529b930e7a05a3dbd2899c7e0271dae8fa5..30509528b8b6ba5a44d32b47e382d8906b61f9d8 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,7 +1,7 @@ module Gitlab class Auth def find(login, password) - user = User.find_by(email: login) || User.find_by(username: login) + user = User.by_login(login) # If no user is found, or it's an LDAP server, try LDAP. # LDAP users are only authenticated via LDAP diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index c2f3b851c070bc93cd8ae958fb24b5a2e95224ef..df1461a45c988e21cb485adab198eb062ac8e790 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -90,7 +90,7 @@ module Grack when *Gitlab::GitAccess::PUSH_COMMANDS if user # Skip user authorization on upload request. - # It will be serverd by update hook in repository + # It will be done by the pre-receive hook in the repository. true else false diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index f95bbde5b399f315e14506dbb08fe4c219ff739c..aabc7f1e69a632cf5c70ba6de5ef0d420234c0ee 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -8,6 +8,13 @@ module Gitlab end end + class << self + def version_required + @version_required ||= File.read(Rails.root. + join('GITLAB_SHELL_VERSION')).strip + end + end + # Init new repository # # name - project path with namespace @@ -16,7 +23,8 @@ module Gitlab # add_repository("gitlab/gitlab-ci") # def add_repository(name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "add-project", "#{name}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, + 'add-project', "#{name}.git"]) end # Import repository @@ -27,7 +35,8 @@ module Gitlab # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # def import_repository(name, url) - system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url, '240' + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project', + "#{name}.git", url, '240']) end # Move repository @@ -39,7 +48,8 @@ module Gitlab # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # def mv_repository(path, new_path) - system "#{gitlab_shell_path}/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project', + "#{path}.git", "#{new_path}.git"]) end # Update HEAD for repository @@ -51,7 +61,8 @@ module Gitlab # update_repository_head("gitlab/gitlab-ci", "3-1-stable") # def update_repository_head(path, branch) - system "#{gitlab_shell_path}/bin/gitlab-projects", "update-head", "#{path}.git", branch + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head', + "#{path}.git", branch]) end # Fork repository to new namespace @@ -63,7 +74,8 @@ module Gitlab # fork_repository("gitlab/gitlab-ci", "randx") # def fork_repository(path, fork_namespace) - system "#{gitlab_shell_path}/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project', + "#{path}.git", fork_namespace]) end # Remove repository from file system @@ -74,7 +86,8 @@ module Gitlab # remove_repository("gitlab/gitlab-ci") # def remove_repository(name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-project", "#{name}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, + 'rm-project', "#{name}.git"]) end # Add repository branch from passed ref @@ -87,7 +100,8 @@ module Gitlab # add_branch("gitlab/gitlab-ci", "4-0-stable", "master") # def add_branch(path, branch_name, ref) - system "#{gitlab_shell_path}/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch', + "#{path}.git", branch_name, ref]) end # Remove repository branch @@ -99,7 +113,8 @@ module Gitlab # rm_branch("gitlab/gitlab-ci", "4-0-stable") # def rm_branch(path, branch_name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch', + "#{path}.git", branch_name]) end # Add repository tag from passed ref @@ -117,7 +132,7 @@ module Gitlab cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git #{tag_name} #{ref}) cmd << message unless message.nil? || message.empty? - system *cmd + Gitlab::Utils.system_silent(cmd) end # Remove repository tag @@ -129,7 +144,8 @@ module Gitlab # rm_tag("gitlab/gitlab-ci", "v4.0") # def rm_tag(path, tag_name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag', + "#{path}.git", tag_name]) end # Add new key to gitlab-shell @@ -138,7 +154,8 @@ module Gitlab # add_key("key-42", "sha-rsa ...") # def add_key(key_id, key_content) - system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content + Gitlab::Utils.system_silent([gitlab_shell_keys_path, + 'add-key', key_id, key_content]) end # Batch-add keys to authorized_keys @@ -157,7 +174,8 @@ module Gitlab # remove_key("key-342", "sha-rsa ...") # def remove_key(key_id, key_content) - system "#{gitlab_shell_path}/bin/gitlab-keys", "rm-key", key_id, key_content + Gitlab::Utils.system_silent([gitlab_shell_keys_path, + 'rm-key', key_id, key_content]) end # Remove all ssh keys from gitlab shell @@ -166,7 +184,7 @@ module Gitlab # remove_all_keys # def remove_all_keys - system "#{gitlab_shell_path}/bin/gitlab-keys", "clear" + Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear']) end # Add empty directory for storing repositories @@ -249,5 +267,13 @@ module Gitlab def exists?(dir_name) File.exists?(full_path(dir_name)) end + + def gitlab_shell_projects_path + File.join(gitlab_shell_path, 'bin', 'gitlab-projects') + end + + def gitlab_shell_keys_path + File.join(gitlab_shell_path, 'bin', 'gitlab-keys') + end end end diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb new file mode 100644 index 0000000000000000000000000000000000000000..67aca5e36e90580f0b381e7231400bd3e14758bc --- /dev/null +++ b/lib/gitlab/git.rb @@ -0,0 +1,5 @@ +module Gitlab + module Git + BLANK_SHA = '0' * 40 + end +end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index b2e8e678d58ff9f482bc29747bc9405ce9aae437..591c9d83c1b443d0944679f8a8775982609be26c 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -67,7 +67,7 @@ module Gitlab if forced_push?(project, oldrev, newrev) :force_push_code_to_protected_branches # and we dont allow remove of protected branch - elsif newrev =~ /0000000/ + elsif newrev == Gitlab::Git::BLANK_SHA :remove_protected_branches else :push_code_to_protected_branches @@ -86,7 +86,7 @@ module Gitlab def forced_push?(project, oldrev, newrev) return false if project.empty_repo? - if oldrev !~ /00000000/ && newrev !~ /00000000/ + if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read missed_refs.split("\n").size > 0 else diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb index fbfed205a0f4c6554727cf7d0c88e1ac5fda7ca3..9e02ccc0f44e2552c4e4df50520da18ec9521782 100644 --- a/lib/gitlab/git_logger.rb +++ b/lib/gitlab/git_logger.rb @@ -1,7 +1,7 @@ module Gitlab class GitLogger < Gitlab::Logger - def self.file_name - 'githost.log' + def self.file_name_noext + 'githost' end def format_message(severity, timestamp, progname, msg) diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb index 13cb08948bb71bad53bc9bddc2a14f09471a7962..39d17def93073286b1f37b899dd7c9bba0e4d76e 100644 --- a/lib/gitlab/git_ref_validator.rb +++ b/lib/gitlab/git_ref_validator.rb @@ -5,7 +5,8 @@ module Gitlab # # Returns true for a valid reference name, false otherwise def validate(ref_name) - system *%W(git check-ref-format refs/#{ref_name}) + Gitlab::Utils.system_silent( + %W(git check-ref-format refs/#{ref_name})) end end end diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb index 0d34976736fc5eedd747eb1ae69674e25466577e..1bec60882925b8d3a129df8676a2b0389ac08ef3 100644 --- a/lib/gitlab/issues_labels.rb +++ b/lib/gitlab/issues_labels.rb @@ -15,7 +15,6 @@ module Gitlab { title: "support", color: yellow }, { title: "discussion", color: blue }, { title: "suggestion", color: blue }, - { title: "feature", color: green }, { title: "enhancement", color: green } ] diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index 8a73ec5038adf0c0635baf7d4579859d57f5e385..59b21149a9a209976e2b5e6394899442a5906291 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -1,5 +1,9 @@ module Gitlab class Logger < ::Logger + def self.file_name + file_name_noext + '.log' + end + def self.error(message) build.error(message) end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index ddcce7557a057a78921f5b5e2d397ad153f4e00d..068c342398b2daa9ce93da2650a06e8ebde3da4e 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -202,7 +202,7 @@ module Gitlab if identifier == "all" link_to("@all", project_url(project), options) - elsif user = User.find_by(username: identifier) + elsif User.find_by(username: identifier) link_to("@#{identifier}", user_url(identifier), options) end end diff --git a/lib/gitlab/production_logger.rb b/lib/gitlab/production_logger.rb new file mode 100644 index 0000000000000000000000000000000000000000..89ce7144b1bce57881ac850ee8cbfbfdcc250adb --- /dev/null +++ b/lib/gitlab/production_logger.rb @@ -0,0 +1,7 @@ +module Gitlab + class ProductionLogger < Gitlab::Logger + def self.file_name_noext + 'production' + end + end +end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 4b8038843b0a88452bebdfd31b251d4bc209848d..c4d0d85b7f58903e4171d8602857aae16622f445 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -67,8 +67,7 @@ module Gitlab def default_regex_message "can contain only letters, digits, '_', '-' and '.'. " \ - "It must start with letter, digit or '_', optionally preceeded by '.'. " \ - "It must not end in '.git'." + "Cannot start with '-' or end in '.git'" \ end def default_regex diff --git a/lib/gitlab/sidekiq_logger.rb b/lib/gitlab/sidekiq_logger.rb new file mode 100644 index 0000000000000000000000000000000000000000..c1dab87a43231c56ebe3ee01e9788771158ace90 --- /dev/null +++ b/lib/gitlab/sidekiq_logger.rb @@ -0,0 +1,7 @@ +module Gitlab + class SidekiqLogger < Gitlab::Logger + def self.file_name_noext + 'sidekiq' + end + end +end diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index de7e040408617995b44ec397f3fad9d34d06008e..877488d847174ef3a59ee91493f41a3006eb108d 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -19,7 +19,7 @@ module Gitlab issue = Issue.find(id) project_issue_url(id: issue.iid, project_id: issue.project, - host: Settings.gitlab['url']) + host: Gitlab.config.gitlab['url']) end end end diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb new file mode 100644 index 0000000000000000000000000000000000000000..bd184c271871e83519f2c847b1f38e1e261a2bf7 --- /dev/null +++ b/lib/gitlab/utils.rb @@ -0,0 +1,13 @@ +module Gitlab + module Utils + extend self + + # Run system command without outputting to stdout. + # + # @param cmd [Array<String>] + # @return [Boolean] + def system_silent(cmd) + Popen::popen(cmd).last.zero? + end + end +end diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 49a68c62293cac41063d0f7c4337a242c8c2c3db..c8b769ace8e0bdce9ed5f32c7d15390786d3932a 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -1,5 +1,5 @@ ## GitLab -## Maintainer: @randx +## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller ## ## Lines starting with two hashes (##) are comments with information. ## Lines starting with one hash (#) are configuration parameters that can be uncommented. @@ -15,7 +15,7 @@ ## - installing an old version of Nginx with the chunkin module [2] compiled in, or ## - using a newer version of Nginx. ## -## At the time of writing we do not know if either of these theoretical solutions works. +## At the time of writing we do not know if either of these theoretical solutions works. ## As a workaround users can use Git over SSH to push large files. ## ## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99 @@ -26,6 +26,7 @@ ## configuration ## ################################### ## +## See installation.md#using-https for additional HTTPS configuration details. upstream gitlab { server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; @@ -33,7 +34,8 @@ upstream gitlab { ## Normal HTTP host server { - listen *:80 default_server; + listen 0.0.0.0:80 default_server; + listen [::]:80 default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice root /home/git/gitlab/public; @@ -42,6 +44,8 @@ server { ## Or if you want to accept large git objects over http client_max_body_size 20m; + ## See app/controllers/application_controller.rb for headers set + ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; error_log /var/log/nginx/gitlab_error.log; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index fd4f93c2f9248a876104d4e6b66fd98fd12e68a1..4e53d5e8b50b1660d8ef328dd4593a194dc6ec33 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -1,5 +1,5 @@ ## GitLab -## Contributors: randx, yin8086, sashkab, orkoden, axilleas +## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller ## ## Modified from nginx http version ## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/ @@ -26,9 +26,8 @@ ## [1] https://github.com/agentzh/chunkin-nginx-module#status ## [2] https://github.com/agentzh/chunkin-nginx-module ## -## ################################### -## SSL configuration ## +## configuration ## ################################### ## ## See installation.md#using-https for additional HTTPS configuration details. @@ -37,22 +36,24 @@ upstream gitlab { server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; } -## Normal HTTP host +## Redirects all HTTP traffic to the HTTPS host server { - listen *:80 default_server; + listen 0.0.0.0:80; + listen [::]:80 default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice - - ## Redirects all traffic to the HTTPS host - root /nowhere; ## root doesn't have to be a valid path since we are redirecting - rewrite ^ https://$server_name$request_uri? permanent; + return 301 https://$server_name$request_uri; + access_log /var/log/nginx/gitlab_access.log; + error_log /var/log/nginx/gitlab_error.log; } + ## HTTPS host server { - listen 443 ssl; + listen 0.0.0.0:443 ssl; + listen [::]:443 ssl default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com - server_tokens off; + server_tokens off; ## Don't show the nginx version number, a security best practice root /home/git/gitlab/public; ## Increase this if you want to upload large attachments @@ -60,24 +61,19 @@ server { client_max_body_size 20m; ## Strong SSL Security - ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/ ssl on; ssl_certificate /etc/nginx/ssl/gitlab.crt; ssl_certificate_key /etc/nginx/ssl/gitlab.key; # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs - ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4'; - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_session_cache builtin:1000 shared:SSL:10m; - - ssl_prefer_server_ciphers on; + ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 5m; - ## [WARNING] The following header states that the browser should only communicate - ## with your server over a secure connection for the next 24 months. - add_header Strict-Transport-Security max-age=63072000; - add_header X-Frame-Options SAMEORIGIN; - add_header X-Content-Type-Options nosniff; + ## See app/controllers/application_controller.rb for headers set ## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL. ## Replace with your ssl_trusted_certificate. For more info see: @@ -88,7 +84,7 @@ server { # ssl_stapling_verify on; # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired - # resolver_timeout 10s; + # resolver_timeout 5s; ## [Optional] Generate a stronger DHE parameter: ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 56e8ff449882264c172b426474b95b538a4fe4bb..7ff23a7600a68c7763eb95bcd9c27295fb106600 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -574,20 +574,16 @@ namespace :gitlab do Gitlab::Shell.new.version end - def required_gitlab_shell_version - File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip - end - def gitlab_shell_major_version - required_gitlab_shell_version.split(".")[0].to_i + Gitlab::Shell.version_required.split('.')[0].to_i end def gitlab_shell_minor_version - required_gitlab_shell_version.split(".")[1].to_i + Gitlab::Shell.version_required.split('.')[1].to_i end def gitlab_shell_patch_version - required_gitlab_shell_version.split(".")[2].to_i + Gitlab::Shell.version_required.split('.')[2].to_i end def has_gitlab_shell3? diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake index 63dcdc523701e32d3111a8d003558b4e9226dd64..189ad6090a41c7c7b7971028257e1fbd431ab86b 100644 --- a/lib/tasks/gitlab/cleanup.rake +++ b/lib/tasks/gitlab/cleanup.rake @@ -92,11 +92,11 @@ namespace :gitlab do User.ldap.each do |ldap_user| print "#{ldap_user.name} (#{ldap_user.extern_uid}) ..." - if Gitlab::LDAP::Access.open { |access| access.allowed?(ldap_user) } + if Gitlab::LDAP::Access.allowed?(ldap_user) puts " [OK]".green else if block_flag - ldap_user.block! + ldap_user.block! unless ldap_user.blocked? puts " [BLOCKED]".red else puts " [NOT IN LDAP]".yellow diff --git a/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake new file mode 100644 index 0000000000000000000000000000000000000000..e9cf0a9b5e862cc16d1b886ecac14ff424d8807c --- /dev/null +++ b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake @@ -0,0 +1,10 @@ +namespace :gitlab do + namespace :db do + task drop_all_postgres_sequences: :environment do + connection = ActiveRecord::Base.connection + connection.execute("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';").each do |sequence| + connection.execute("DROP SEQUENCE #{sequence['relname']}") + end + end + end +end diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index b6ed874e11a5a315893b2fbb643fd1c19b021fdc..3c693546c098886d62636ebc4d804a747c2610f0 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -15,26 +15,17 @@ namespace :gitlab do git_base_path = Gitlab.config.gitlab_shell.repos_path repos_to_import = Dir.glob(git_base_path + '/**/*.git') - namespaces = Namespace.pluck(:path) - repos_to_import.each do |repo_path| # strip repo base path repo_path[0..git_base_path.length] = '' path = repo_path.sub(/\.git$/, '') - name = File.basename path - group_name = File.dirname path + group_name, name = File.split(path) group_name = nil if group_name == '.' - # Skip if group or user - if namespaces.include?(name) - puts "Skipping #{project.name} due to namespace conflict with group or user".yellow - next - end - puts "Processing #{repo_path}".yellow - if path =~ /.wiki\Z/ + if path =~ /\.wiki\Z/ puts " * Skipping wiki repo" next end @@ -53,9 +44,9 @@ namespace :gitlab do # find group namespace if group_name - group = Group.find_by(path: group_name) + group = Namespace.find_by(path: group_name) # create group namespace - if !group + unless group group = Group.new(:name => group_name) group.path = group_name group.owner = user diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index a8f26a7c02931f8484aea7d0d3d86a1f3e196ee0..202e55c89adb4d2b1658f3e12bea8bb8e18635dc 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -4,20 +4,20 @@ namespace :gitlab do task :install, [:tag, :repo] => :environment do |t, args| warn_user_is_not_gitlab - default_version = File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip + default_version = Gitlab::Shell.version_required args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git") - user = Settings.gitlab.user - home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Settings.gitlab.user_home - gitlab_url = Settings.gitlab.url + user = Gitlab.config.gitlab.user + home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home + gitlab_url = Gitlab.config.gitlab.url # gitlab-shell requires a / at the end of the url - gitlab_url += "/" unless gitlab_url.match(/\/$/) + gitlab_url += '/' unless gitlab_url.end_with?('/') repos_path = Gitlab.config.gitlab_shell.repos_path target_dir = Gitlab.config.gitlab_shell.path # Clone if needed unless File.directory?(target_dir) - sh "git clone '#{args.repo}' '#{target_dir}'" + sh(*%W(git clone #{args.repo} #{target_dir})) end # Make sure we're on the right tag @@ -76,7 +76,7 @@ namespace :gitlab do desc "GITLAB | Build missing projects" task build_missing_projects: :environment do Project.find_each(batch_size: 1000) do |project| - path_to_repo = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") + path_to_repo = project.repository.path_to_repo if File.exists?(path_to_repo) print '-' else diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 98ba5a47ee53c8108830df92533ee96869952fb8..d291621935b718f2593a42fa2ff25b9b18c6b36d 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe "Projects", feature: true do +describe "Projects", feature: true, js: true do before { login_as :user } describe "DELETE /projects/:id" do @@ -10,12 +10,23 @@ describe "Projects", feature: true do visit edit_project_path(@project) end - it "should be correct path", js: true do - expect { - click_link "Remove project" - fill_in 'confirm_name_input', with: @project.path - click_button 'Confirm' - }.to change {Project.count}.by(-1) + it "should remove project" do + expect { remove_project }.to change {Project.count}.by(-1) end + + it 'should delete the project from disk' do + expect(GitlabShellWorker).to( + receive(:perform_async).with(:remove_repository, + /#{@project.path_with_namespace}/) + ).twice + + remove_project + end + end + + def remove_project + click_link "Remove project" + fill_in 'confirm_name_input', with: @project.path + click_button 'Confirm' end end diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 7b831c48611f67a675796eba5670a1777070198c..a1206989d39fbaf97e6e6c42eae0f32a3beb7cf4 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -11,7 +11,7 @@ describe 'Users', feature: true do fill_in "user_name", with: "Name Surname" fill_in "user_username", with: "Great" fill_in "user_email", with: "name@mail.com" - fill_in "user_password", with: "password1234" + fill_in "user_password_sign_up", with: "password1234" fill_in "user_password_confirmation", with: "password1234" expect { click_button "Sign up" }.to change {User.count}.by(1) end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 6038dc27c7e56198a9f2e0d65b1061e1475ea9b1..b54811a109c33e628fc6a5190ccc30f779c45d62 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -640,7 +640,9 @@ describe GitlabMarkdownHelper do end it "should generate absolute urls for emoji" do - markdown(":smile:").should include("src=\"http://localhost/assets/emoji/smile.png") + markdown(':smile:').should( + include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/smile.png)) + ) end it "should generate absolute urls for emoji if relative url is present" do diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..8bf6ee2ed50851d2c37746442366da96d0f539e6 --- /dev/null +++ b/spec/lib/disable_email_interceptor_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe DisableEmailInterceptor do + before do + ActionMailer::Base.register_interceptor(DisableEmailInterceptor) + end + + it 'should not send emails' do + Gitlab.config.gitlab.stub(:email_enabled).and_return(false) + expect { + deliver_mail + }.not_to change(ActionMailer::Base.deliveries, :count) + end + + after do + # Removing interceptor from the list because unregister_interceptor is + # implemented in later version of mail gem + # See: https://github.com/mikel/mail/pull/705 + Mail.class_variable_set(:@@delivery_interceptors, []) + end + + def deliver_mail + key = create :personal_key + Notify.new_ssh_key_email(key.id) + end +end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 1f3e1a4a3c1193fa9f103b2c86ad3aebbcfe02fc..95fc7e16a1102b8838a0395af3eb6fbd54352cd8 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -10,13 +10,21 @@ describe Gitlab::Auth do password: password, password_confirmation: password) end - let(:username) { 'john' } + let(:username) { 'John' } # username isn't lowercase, test this let(:password) { 'my-secret' } it "should find user by valid login/password" do expect( gl_auth.find(username, password) ).to eql user end + it 'should find user by valid email/password with case-insensitive email' do + expect(gl_auth.find(user.email.upcase, password)).to eql user + end + + it 'should find user by valid username/password with case-insensitive username' do + expect(gl_auth.find(username.upcase, password)).to eql user + end + it "should not find user with invalid password" do password = 'wrong' expect( gl_auth.find(username, password) ).to_not eql user diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 2fffa26f47e479bd8743e4849ba8d8445165376e..da97208e860bb189f7c976da6b2783744ae2bd35 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -55,12 +55,13 @@ describe Gitlab::GitAccess do def changes { - push_new_branch: '000000000 570e7b2ab refs/heads/wow', + push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow", push_master: '6f6d7e7ed 570e7b2ab refs/heads/master', push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature', - push_remove_protected_branch: '570e7b2ab 000000000 refs/heads/feature', + push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\ + 'refs/heads/feature', push_tag: '6f6d7e7ed 570e7b2ab refs/tags/v1.0.0', - push_new_tag: '000000000 570e7b2ab refs/tags/v7.8.9', + push_new_tag: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/tags/v7.8.9", push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'] } end diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index f5fa13eb9063510c0beef0f0a359a7668a7dc144..f4e66c528ad482ab76b368165aa9d4c4ce923ab0 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -12,3 +12,16 @@ describe Mentionable do end end end + +describe Issue, "Mentionable" do + describe :mentioned_users do + let!(:user) { create(:user, username: 'stranger') } + let!(:user2) { create(:user, username: 'john') } + let!(:issue) { create(:issue, description: '@stranger mentioned') } + + subject { issue.mentioned_users } + + it { should include(user) } + it { should_not include(user2) } + end +end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 1fdd959da9dc6720d4e56fb5dce9776877906467..204ae9da7042bc89fe1dc4b6863d3e0d16f57e50 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -36,7 +36,7 @@ describe Event do @user = project.owner data = { - before: "0000000000000000000000000000000000000000", + before: Gitlab::Git::BLANK_SHA, after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", ref: "refs/heads/master", user_id: @user.id, @@ -60,7 +60,6 @@ describe Event do it { @event.push?.should be_true } it { @event.proper?.should be_true } - it { @event.new_branch?.should be_true } it { @event.tag?.should be_false } it { @event.branch_name.should == "master" } it { @event.author.should == @user } diff --git a/spec/models/gitlab_ci_service_spec.rb b/spec/models/gitlab_ci_service_spec.rb index ebc377047be6230c254e7de2ed921e3aff6aca09..83277058fbb183a838d3fc96a564a5c6feb68b51 100644 --- a/spec/models/gitlab_ci_service_spec.rb +++ b/spec/models/gitlab_ci_service_spec.rb @@ -34,11 +34,11 @@ describe GitlabCiService do end describe :commit_status_path do - it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c/status.json?token=verySecret"} + it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/commits/2ab7834c/status.json?token=verySecret"} end describe :build_page do - it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c"} + it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/commits/2ab7834c"} end end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 2d839e9611b772dbaba9249bbb7e0b7539d8cb0e..6ab7162c15c5b470036171f7fe3784a2446206e6 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -249,6 +249,12 @@ describe Note do its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" } end + context 'commit contained in a merge request' do + subject { Note.create_cross_reference_note(mergereq.commits.first, mergereq, author, project) } + + it { should be_nil } + end + context 'commit from issue' do subject { Note.create_cross_reference_note(commit, issue, author, project) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 48b58400a1ecfdc54e0bdece8be8da77e3811cb2..70a15cac1a888bd0644ae2a09513e1b2f8d6eab5 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -145,63 +145,6 @@ describe Project do end end - describe 'comment merge requests with commits' do - before do - @user = create(:user) - group = create(:group) - group.add_owner(@user) - - @project = create(:project, namespace: group) - @fork_project = Projects::ForkService.new(@project, @user).execute - @merge_request = create(:merge_request, source_project: @project, - source_branch: 'master', - target_branch: 'feature', - target_project: @project) - @fork_merge_request = create(:merge_request, source_project: @fork_project, - source_branch: 'master', - target_branch: 'feature', - target_project: @project) - - @commits = @merge_request.commits - end - - context 'push to origin repo source branch' do - before do - @project.comment_mr_with_commits('master', @commits, @user) - end - - it { @merge_request.notes.should_not be_empty } - it { @fork_merge_request.notes.should be_empty } - end - - context 'push to origin repo target branch' do - before do - @project.comment_mr_with_commits('feature', @commits, @user) - end - - it { @merge_request.notes.should be_empty } - it { @fork_merge_request.notes.should be_empty } - end - - context 'push to fork repo source branch' do - before do - @fork_project.comment_mr_with_commits('master', @commits, @user) - end - - it { @merge_request.notes.should be_empty } - it { @fork_merge_request.notes.should_not be_empty } - end - - context 'push to fork repo target branch' do - before do - @fork_project.comment_mr_with_commits('feature', @commits, @user) - end - - it { @merge_request.notes.should be_empty } - it { @fork_merge_request.notes.should be_empty } - end - end - describe :find_with_namespace do context 'with namespace' do before do diff --git a/spec/models/slack_message_spec.rb b/spec/models/slack_message_spec.rb index 1cd585347020a4969def81da2dcb62c4f17f0377..c530fad619b735bef379aa8b0845300caf8b8f17 100644 --- a/spec/models/slack_message_spec.rb +++ b/spec/models/slack_message_spec.rb @@ -1,4 +1,4 @@ -require_relative '../../app/models/project_services/slack_message' +require 'spec_helper' describe SlackMessage do subject { SlackMessage.new(args) } @@ -26,11 +26,11 @@ describe SlackMessage do it 'returns a message regarding pushes' do subject.pretext.should == - 'user_name pushed to branch <url/commits/master|master> of ' << + 'user_name pushed to branch <url/commits/master|master> of '\ '<url|project_name> (<url/compare/before...after|Compare changes>)' subject.attachments.should == [ { - text: "<url1|abcdefghi>: message1 - author1\n" << + text: "<url1|abcdefghi>: message1 - author1\n"\ "<url2|123456789>: message2 - author2", color: color, } @@ -45,7 +45,7 @@ describe SlackMessage do it 'returns a message regarding a new branch' do subject.pretext.should == - 'user_name pushed new branch <url/commits/master|master> to ' << + 'user_name pushed new branch <url/commits/master|master> to '\ '<url|project_name>' subject.attachments.should be_empty end diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb index 526165e397c66a0897b98dda25b4ffe361e9e556..d48403919676414a3e0ea855ee3abf9608ca401f 100644 --- a/spec/models/slack_service_spec.rb +++ b/spec/models/slack_service_spec.rb @@ -31,71 +31,27 @@ describe SlackService do end describe "Execute" do - let(:slack) { SlackService.new } - let(:slack_service) { SlackService.new } - let(:user) { create(:user) } + let(:slack) { SlackService.new } + let(:user) { create(:user) } let(:project) { create(:project) } let(:sample_data) { GitPushService.new.sample_data(project, user) } - let(:webhook) { 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' } - let(:new_webhook) { 'https://hooks.gitlabhq.slack.com/services/cdIj4r4LfXUOySDUjp0tk3OI' } - let(:api_url) { - 'https://gitlabhq.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI' - } + let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' } before do slack.stub( project: project, project_id: project.id, service_hook: true, - webhook: webhook + webhook: webhook_url ) - WebMock.stub_request(:post, api_url) + WebMock.stub_request(:post, webhook_url) end it "should call Slack API" do slack.execute(sample_data) - WebMock.should have_requested(:post, api_url).once - end - - context 'with new webhook syntax' do - before do - slack_service.stub( - project: project, - project_id: project.id, - service_hook: true, - webhook: new_webhook - ) - - WebMock.stub_request(:post, api_url) - end - - it "should call Slack API" do - slack_service.execute(sample_data) - - WebMock.should have_requested(:post, api_url).once - end - end - - context 'with new webhook syntax with slack allowed team name' do - before do - @allowed_webhook = 'https://gitlab-hq-123.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI' - slack_service.stub( - project: project, - project_id: project.id, - service_hook: true, - webhook: @allowed_webhook - ) - - WebMock.stub_request(:post, @allowed_webhook) - end - - it "should call Slack API" do - slack_service.execute(sample_data) - - WebMock.should have_requested(:post, @allowed_webhook).once - end + WebMock.should have_requested(:post, webhook_url).once end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0ff84b837509893ccad36bdf849f38a04c311c3c..ccb1b1bcfeadf0898a8bc83db9d99dee4f6d96a9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -287,6 +287,20 @@ describe User do end end + describe '.by_login' do + let(:username) { 'John' } + let!(:user) { create(:user, username: username) } + + it 'should get the correct user' do + expect(User.by_login(user.email.upcase)).to eq user + expect(User.by_login(user.email)).to eq user + expect(User.by_login(username.downcase)).to eq user + expect(User.by_login(username)).to eq user + expect(User.by_login(nil)).to be_nil + expect(User.by_login('')).to be_nil + end + end + describe 'all_ssh_keys' do it { should have_many(:keys).dependent(:destroy) } diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 8834a6cfa833d6e5ac8b5864f9ad039afcf71c93..b45572c39fd18995b37322c2e69cea37e107cc5e 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -146,6 +146,7 @@ describe API::API, api: true do it "should remove branch" do delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user) response.status.should == 200 + json_response['branch_name'].should == branch_name end it 'should return 404 if branch not exists' do diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 38e0a284c36e57fafca6e6a43ee3ad8ba1a1e60d..a3f58f509132c9dd90b046fc5f55b5628faca54a 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -8,6 +8,7 @@ describe API::API, api: true do let!(:project) { create(:project, creator_id: user.id) } let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } + let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } before { project.team << [user, :reporter] } @@ -81,4 +82,68 @@ describe API::API, api: true do end end end + + describe 'GET /projects:id/repository/commits/:sha/comments' do + context 'authorized user' do + it 'should return merge_request comments' do + get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) + response.status.should == 200 + json_response.should be_an Array + json_response.length.should == 1 + json_response.first['note'].should == 'a comment on a commit' + json_response.first['author']['id'].should == user.id + end + + it 'should return a 404 error if merge_request_id not found' do + get api("/projects/#{project.id}/repository/commits/1234ab/comments", user) + response.status.should == 404 + end + end + + context 'unauthorized user' do + it 'should not return the diff of the selected commit' do + get api("/projects/#{project.id}/repository/commits/1234ab/comments") + response.status.should == 401 + end + end + end + + describe 'POST /projects:id/repository/commits/:sha/comments' do + context 'authorized user' do + it 'should return comment' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment' + response.status.should == 201 + json_response['note'].should == 'My comment' + json_response['path'].should be_nil + json_response['line'].should be_nil + json_response['line_type'].should be_nil + end + + it 'should return the inline comment' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.diffs.first.new_path, line: 7, line_type: 'new' + response.status.should == 201 + json_response['note'].should == 'My comment' + json_response['path'].should == project.repository.commit.diffs.first.new_path + json_response['line'].should == 7 + json_response['line_type'].should == 'new' + end + + it 'should return 400 if note is missing' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) + response.status.should == 400 + end + + it 'should return 404 if note is attached to non existent commit' do + post api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment' + response.status.should == 404 + end + end + + context 'unauthorized user' do + it 'should not return the diff of the selected commit' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments") + response.status.should == 401 + end + end + end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index fe0028439c68a52c6f4b8a7d70aca846656b859f..1c6c0925f45e821fc75b5f56cfc898e02b0450ef 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -203,15 +203,12 @@ describe API::API, api: true do json_response['message']['name'].should == [ 'can\'t be blank', 'is too short (minimum is 0 characters)', - 'can contain only letters, digits, \'_\', \'-\' and \'.\' and '\ - 'space. It must start with letter, digit or \'_\'.' + Gitlab::Regex.project_regex_message ] json_response['message']['path'].should == [ 'can\'t be blank', 'is too short (minimum is 0 characters)', - 'can contain only letters, digits, \'_\', \'-\' and \'.\'. It must '\ - 'start with letter, digit or \'_\', optionally preceeded by \'.\'. '\ - 'It must not end in \'.git\'.' + Gitlab::Regex.send(:default_regex_message) ] end @@ -339,6 +336,7 @@ describe API::API, api: true do json_event['action_name'].should == 'joined' json_event['project_id'].to_i.should == project.id + json_event['author_username'].should == user.username end it "should return a 404 error if not found" do @@ -663,6 +661,11 @@ describe API::API, api: true do describe "DELETE /projects/:id" do context "when authenticated as user" do it "should remove project" do + expect(GitlabShellWorker).to( + receive(:perform_async).with(:remove_repository, + /#{project.path_with_namespace}/) + ).twice + delete api("/projects/#{project.id}", user) response.status.should == 200 end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 6e54839b67777de7fea1a7a327bd9dd5606a289e..beae71c02d9343b5c73939f32205a62fc97c2ec5 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -34,21 +34,23 @@ describe API::API, api: true do end end - # TODO: fix this test for CI - #context 'annotated tag' do - #it 'should create a new annotated tag' do - #post api("/projects/#{project.id}/repository/tags", user), - #tag_name: 'v7.1.0', - #ref: 'master', - #message: 'tag message' - - #response.status.should == 201 - #json_response['name'].should == 'v7.1.0' - # The message is not part of the JSON response. - # Additional changes to the gitlab_git gem may be required. - # json_response['message'].should == 'tag message' - #end - #end + context 'annotated tag' do + it 'should create a new annotated tag' do + # Identity must be set in .gitconfig to create annotated tag. + repo_path = project.repository.path_to_repo + system(*%W(git --git-dir=#{repo_path} config user.name #{user.name})) + system(*%W(git --git-dir=#{repo_path} config user.email #{user.email})) + + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.1.0', + ref: 'master', + message: 'Release 7.1.0' + + response.status.should == 201 + json_response['name'].should == 'v7.1.0' + json_response['message'].should == 'Release 7.1.0' + end + end it 'should deny for user without push access' do post api("/projects/#{project.id}/repository/tags", user2), diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index f883c9e028ad9a87739a15ff3f8033ebb29a2bcc..d8282d0696bd92fda584150692deaf2bfd938747 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -27,4 +27,30 @@ describe API::API, api: true do project.gitlab_ci_service.should be_nil end end + + describe 'PUT /projects/:id/services/hipchat' do + it 'should update hipchat settings' do + put api("/projects/#{project.id}/services/hipchat", user), + token: 'secret-token', room: 'test' + + response.status.should == 200 + project.hipchat_service.should_not be_nil + end + + it 'should return if required fields missing' do + put api("/projects/#{project.id}/services/gitlab-ci", user), + token: 'secret-token', active: true + + response.status.should == 400 + end + end + + describe 'DELETE /projects/:id/services/hipchat' do + it 'should delete hipchat settings' do + delete api("/projects/#{project.id}/services/hipchat", user) + + response.status.should == 200 + project.hipchat_service.should be_nil + end + end end diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb index 013f425d6ce6f4cb1e1601e304b0d7f64d0e9906..57b2e6cbd6abcd12b815c260481acb4ef1dd15b1 100644 --- a/spec/requests/api/session_spec.rb +++ b/spec/requests/api/session_spec.rb @@ -19,6 +19,32 @@ describe API::API, api: true do end end + context 'when email has case-typo and password is valid' do + it 'should return private token' do + post api('/session'), email: user.email.upcase, password: '12345678' + expect(response.status).to eq 201 + + expect(json_response['email']).to eq user.email + expect(json_response['private_token']).to eq user.private_token + expect(json_response['is_admin']).to eq user.is_admin? + expect(json_response['can_create_project']).to eq user.can_create_project? + expect(json_response['can_create_group']).to eq user.can_create_group? + end + end + + context 'when login has case-typo and password is valid' do + it 'should return private token' do + post api('/session'), login: user.username.upcase, password: '12345678' + expect(response.status).to eq 201 + + expect(json_response['email']).to eq user.email + expect(json_response['private_token']).to eq user.private_token + expect(json_response['is_admin']).to eq user.is_admin? + expect(json_response['can_create_project']).to eq user.can_create_project? + expect(json_response['can_create_group']).to eq user.can_create_group? + end + end + context "when invalid password" do it "should return authentication error" do post api("/session"), email: user.email, password: '123' diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index bc1598273bed59f0c19524fe114eed73cf02daf8..113a39b870ed9e5e85d293a51458ef1426650a5f 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -140,9 +140,7 @@ describe API::API, api: true do json_response['message']['projects_limit']. should == ['must be greater than or equal to 0'] json_response['message']['username']. - should == ['can contain only letters, digits, '\ - '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\ - '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.'] + should == [Gitlab::Regex.send(:default_regex_message)] end it "shouldn't available for non admin users" do @@ -284,9 +282,7 @@ describe API::API, api: true do json_response['message']['projects_limit']. should == ['must be greater than or equal to 0'] json_response['message']['username']. - should == ['can contain only letters, digits, '\ - '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\ - '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.'] + should == [Gitlab::Regex.send(:default_regex_message)] end context "with existing user" do @@ -433,6 +429,7 @@ describe API::API, api: true do json_response['is_admin'].should == user.is_admin? json_response['can_create_project'].should == user.can_create_project? json_response['can_create_group'].should == user.can_create_group? + json_response['projects_limit'].should == user.projects_limit end it "should return 401 error if user is unauthenticated" do diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 4ef053a767fd5d77f5509107c7b79dd187cac08a..19b442573f4febf814d949b200e2eeec9c6d9ba9 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -8,7 +8,7 @@ describe GitPushService do let (:service) { GitPushService.new } before do - @blankrev = '0000000000000000000000000000000000000000' + @blankrev = Gitlab::Git::BLANK_SHA @oldrev = sample_commit.parent_id @newrev = sample_commit.id @ref = 'refs/heads/master' diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..9f2941520532aef3e771aa60b742809f7a40066e --- /dev/null +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +describe MergeRequests::RefreshService do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:service) { MergeRequests::RefreshService } + + describe :execute do + before do + @user = create(:user) + group = create(:group) + group.add_owner(@user) + + @project = create(:project, namespace: group) + @fork_project = Projects::ForkService.new(@project, @user).execute + @merge_request = create(:merge_request, source_project: @project, + source_branch: 'master', + target_branch: 'feature', + target_project: @project) + + @fork_merge_request = create(:merge_request, source_project: @fork_project, + source_branch: 'master', + target_branch: 'feature', + target_project: @project) + + @commits = @merge_request.commits + + @oldrev = @commits.last.id + @newrev = @commits.first.id + end + + context 'push to origin repo source branch' do + before do + service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + + it { @merge_request.notes.should_not be_empty } + it { @merge_request.should be_open } + it { @fork_merge_request.should be_open } + it { @fork_merge_request.notes.should be_empty } + end + + context 'push to origin repo target branch' do + before do + service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature') + reload_mrs + end + + it { @merge_request.notes.should be_empty } + it { @merge_request.should be_merged } + it { @fork_merge_request.should be_merged } + it { @fork_merge_request.notes.should be_empty } + end + + context 'push to fork repo source branch' do + before do + service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + + it { @merge_request.notes.should be_empty } + it { @merge_request.should be_open } + it { @fork_merge_request.notes.should_not be_empty } + it { @fork_merge_request.should be_open } + end + + context 'push to fork repo target branch' do + before do + service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/feature') + reload_mrs + end + + it { @merge_request.notes.should be_empty } + it { @merge_request.should be_open } + it { @fork_merge_request.notes.should be_empty } + it { @fork_merge_request.should be_open } + end + + context 'push to origin repo target branch after fork project was removed' do + before do + @fork_project.destroy + service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature') + reload_mrs + end + + it { @merge_request.notes.should be_empty } + it { @merge_request.should be_merged } + it { @fork_merge_request.should be_open } + it { @fork_merge_request.notes.should be_empty } + end + + def reload_mrs + @merge_request.reload + @fork_merge_request.reload + end + end +end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 2508dfc45654622ddfa19f37abf6f002ff7d7159..79d0526ff89cb3b1d2a8793a3b456f304b2a426e 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -3,15 +3,12 @@ require 'spec_helper' describe Projects::TransferService do let(:user) { create(:user) } let(:group) { create(:group) } - let(:group2) { create(:group) } let(:project) { create(:project, namespace: user.namespace) } context 'namespace -> namespace' do before do group.add_owner(user) - @service = Projects::TransferService.new(project, user, namespace_id: group.id) - @service.gitlab_shell.stub(mv_repository: true) - @result = @service.execute + @result = transfer_project(project, user, namespace_id: group.id) end it { @result.should be_true } @@ -20,24 +17,25 @@ describe Projects::TransferService do context 'namespace -> no namespace' do before do - group.add_owner(user) - @service = Projects::TransferService.new(project, user, namespace_id: nil) - @service.gitlab_shell.stub(mv_repository: true) - @result = @service.execute + @result = transfer_project(project, user, namespace_id: nil) end + it { @result.should_not be_nil } # { result.should be_false } passes on nil it { @result.should be_false } it { project.namespace.should == user.namespace } end context 'namespace -> not allowed namespace' do before do - @service = Projects::TransferService.new(project, user, namespace_id: group2.id) - @service.gitlab_shell.stub(mv_repository: true) - @result = @service.execute + @result = transfer_project(project, user, namespace_id: group.id) end + it { @result.should_not be_nil } # { result.should be_false } passes on nil it { @result.should be_false } it { project.namespace.should == user.namespace } end + + def transfer_project(project, user, params) + Projects::TransferService.new(project, user, params).execute + end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 5f55871dc4a67870345593d3d57df3b7a701a8b7..e6db410fb1cad0f43978e7cd5c2e08994a2659f4 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -3,6 +3,16 @@ require 'rspec/mocks' module TestEnv extend self + # When developing the seed repository, comment out the branch you will modify. + BRANCH_SHA = { + 'feature' => '0b4bc9a', + 'feature_conflict' => 'bb5206f', + 'fix' => '12d65c8', + 'improve/awesome' => '5937ac0', + 'markdown' => '0ed8c6c', + 'master' => '5937ac0' + } + # Test environment # # See gitlab.yml.example test section for paths @@ -18,13 +28,13 @@ module TestEnv if File.directory?(tmp_test_path) Dir.entries(tmp_test_path).each do |entry| - unless ['.', '..', 'gitlab-shell'].include?(entry) + unless ['.', '..', 'gitlab-shell', factory_repo_name].include?(entry) FileUtils.rm_r(File.join(tmp_test_path, entry)) end end end - FileUtils.mkdir_p(tmp_test_path) + FileUtils.mkdir_p(repos_path) # Setup GitLab shell for test instance setup_gitlab_shell @@ -49,13 +59,32 @@ module TestEnv clone_url = "https://gitlab.com/gitlab-org/#{factory_repo_name}.git" unless File.directory?(factory_repo_path) - git_cmd = %W(git clone --bare #{clone_url} #{factory_repo_path}) - system(*git_cmd) + system(*%W(git clone #{clone_url} #{factory_repo_path})) + end + + Dir.chdir(factory_repo_path) do + BRANCH_SHA.each do |branch, sha| + # Try to reset without fetching to avoid using the network. + reset = %W(git update-ref refs/heads/#{branch} #{sha}) + unless system(*reset) + if system(*%w(git fetch origin)) + unless system(*reset) + raise 'The fetched test seed '\ + 'does not contain the required revision.' + end + else + raise 'Could not fetch test seed repository.' + end + end + end end + + # We must copy bare repositories because we will push to them. + system(*%W(git clone --bare #{factory_repo_path} #{factory_repo_path_bare})) end def copy_repo(project) - base_repo_path = File.expand_path(factory_repo_path) + base_repo_path = File.expand_path(factory_repo_path_bare) target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git") FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) @@ -69,7 +98,11 @@ module TestEnv private def factory_repo_path - @factory_repo_path ||= repos_path + "/root/#{factory_repo_name}.git" + @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name) + end + + def factory_repo_path_bare + factory_repo_path.to_s + '_bare' end def factory_repo_name diff --git a/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js b/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js new file mode 100644 index 0000000000000000000000000000000000000000..ee374a07fab69a5e925e79c69029bf9d0f9c30c6 --- /dev/null +++ b/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js @@ -0,0 +1,659 @@ +/*! + * jQuery Password Strength plugin for Twitter Bootstrap + * + * Copyright (c) 2008-2013 Tane Piper + * Copyright (c) 2013 Alejandro Blanco + * Dual licensed under the MIT and GPL licenses. + */ + +(function (jQuery) { +// Source: src/rules.js + + var rulesEngine = {}; + + try { + if (!jQuery && module && module.exports) { + var jQuery = require("jquery"), + jsdom = require("jsdom").jsdom; + jQuery = jQuery(jsdom().parentWindow); + } + } catch (ignore) {} + + (function ($, rulesEngine) { + "use strict"; + var validation = {}; + + rulesEngine.forbiddenSequences = [ + "0123456789", "abcdefghijklmnopqrstuvwxyz", "qwertyuiop", "asdfghjkl", + "zxcvbnm", "!@#$%^&*()_+" + ]; + + validation.wordNotEmail = function (options, word, score) { + if (word.match(/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i)) { + return score; + } + return 0; + }; + + validation.wordLength = function (options, word, score) { + var wordlen = word.length, + lenScore = Math.pow(wordlen, options.rules.raisePower); + if (wordlen < options.common.minChar) { + lenScore = (lenScore + score); + } + return lenScore; + }; + + validation.wordSimilarToUsername = function (options, word, score) { + var username = $(options.common.usernameField).val(); + if (username && word.toLowerCase().match(username.toLowerCase())) { + return score; + } + return 0; + }; + + validation.wordTwoCharacterClasses = function (options, word, score) { + if (word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) || + (word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) || + (word.match(/(.[!,@,#,$,%,\^,&,*,?,_,~])/) && word.match(/[a-zA-Z0-9_]/))) { + return score; + } + return 0; + }; + + validation.wordRepetitions = function (options, word, score) { + if (word.match(/(.)\1\1/)) { return score; } + return 0; + }; + + validation.wordSequences = function (options, word, score) { + var found = false, + j; + if (word.length > 2) { + $.each(rulesEngine.forbiddenSequences, function (idx, seq) { + var sequences = [seq, seq.split('').reverse().join('')]; + $.each(sequences, function (idx, sequence) { + for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3: + if (sequence.indexOf(word.toLowerCase().substring(j, j + 3)) > -1) { + found = true; + } + } + }); + }); + if (found) { return score; } + } + return 0; + }; + + validation.wordLowercase = function (options, word, score) { + return word.match(/[a-z]/) && score; + }; + + validation.wordUppercase = function (options, word, score) { + return word.match(/[A-Z]/) && score; + }; + + validation.wordOneNumber = function (options, word, score) { + return word.match(/\d+/) && score; + }; + + validation.wordThreeNumbers = function (options, word, score) { + return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score; + }; + + validation.wordOneSpecialChar = function (options, word, score) { + return word.match(/.[!,@,#,$,%,\^,&,*,?,_,~]/) && score; + }; + + validation.wordTwoSpecialChar = function (options, word, score) { + return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score; + }; + + validation.wordUpperLowerCombo = function (options, word, score) { + return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score; + }; + + validation.wordLetterNumberCombo = function (options, word, score) { + return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score; + }; + + validation.wordLetterNumberCharCombo = function (options, word, score) { + return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score; + }; + + rulesEngine.validation = validation; + + rulesEngine.executeRules = function (options, word) { + var totalScore = 0; + + $.each(options.rules.activated, function (rule, active) { + if (active) { + var score = options.rules.scores[rule], + funct = rulesEngine.validation[rule], + result, + errorMessage; + + if (!$.isFunction(funct)) { + funct = options.rules.extra[rule]; + } + + if ($.isFunction(funct)) { + result = funct(options, word, score); + if (result) { + totalScore += result; + } + if (result < 0 || (!$.isNumeric(result) && !result)) { + errorMessage = options.ui.spanError(options, rule); + if (errorMessage.length > 0) { + options.instances.errors.push(errorMessage); + } + } + } + } + }); + + return totalScore; + }; + }(jQuery, rulesEngine)); + + try { + if (module && module.exports) { + module.exports = rulesEngine; + } + } catch (ignore) {} + +// Source: src/options.js + + + + + var defaultOptions = {}; + + defaultOptions.common = {}; + defaultOptions.common.minChar = 6; + defaultOptions.common.usernameField = "#username"; + defaultOptions.common.userInputs = [ + // Selectors for input fields with user input + ]; + defaultOptions.common.onLoad = undefined; + defaultOptions.common.onKeyUp = undefined; + defaultOptions.common.zxcvbn = false; + defaultOptions.common.debug = false; + + defaultOptions.rules = {}; + defaultOptions.rules.extra = {}; + defaultOptions.rules.scores = { + wordNotEmail: -100, + wordLength: -50, + wordSimilarToUsername: -100, + wordSequences: -50, + wordTwoCharacterClasses: 2, + wordRepetitions: -25, + wordLowercase: 1, + wordUppercase: 3, + wordOneNumber: 3, + wordThreeNumbers: 5, + wordOneSpecialChar: 3, + wordTwoSpecialChar: 5, + wordUpperLowerCombo: 2, + wordLetterNumberCombo: 2, + wordLetterNumberCharCombo: 2 + }; + defaultOptions.rules.activated = { + wordNotEmail: true, + wordLength: true, + wordSimilarToUsername: true, + wordSequences: true, + wordTwoCharacterClasses: false, + wordRepetitions: false, + wordLowercase: true, + wordUppercase: true, + wordOneNumber: true, + wordThreeNumbers: true, + wordOneSpecialChar: true, + wordTwoSpecialChar: true, + wordUpperLowerCombo: true, + wordLetterNumberCombo: true, + wordLetterNumberCharCombo: true + }; + defaultOptions.rules.raisePower = 1.4; + + defaultOptions.ui = {}; + defaultOptions.ui.bootstrap2 = false; + defaultOptions.ui.showProgressBar = true; + defaultOptions.ui.showPopover = false; + defaultOptions.ui.showStatus = false; + defaultOptions.ui.spanError = function (options, key) { + "use strict"; + var text = options.ui.errorMessages[key]; + if (!text) { return ''; } + return '<span style="color: #d52929">' + text + '</span>'; + }; + defaultOptions.ui.errorMessages = { + wordLength: "Your password is too short", + wordNotEmail: "Do not use your email as your password", + wordSimilarToUsername: "Your password cannot contain your username", + wordTwoCharacterClasses: "Use different character classes", + wordRepetitions: "Too many repetitions", + wordSequences: "Your password contains sequences" + }; + defaultOptions.ui.verdicts = ["Weak", "Normal", "Medium", "Strong", "Very Strong"]; + defaultOptions.ui.showVerdicts = true; + defaultOptions.ui.showVerdictsInsideProgressBar = false; + defaultOptions.ui.showErrors = false; + defaultOptions.ui.container = undefined; + defaultOptions.ui.viewports = { + progress: undefined, + verdict: undefined, + errors: undefined + }; + defaultOptions.ui.scores = [14, 26, 38, 50]; + +// Source: src/ui.js + + + + + var ui = {}; + + (function ($, ui) { + "use strict"; + + var barClasses = ["danger", "warning", "success"], + statusClasses = ["error", "warning", "success"]; + + ui.getContainer = function (options, $el) { + var $container; + + $container = $(options.ui.container); + if (!($container && $container.length === 1)) { + $container = $el.parent(); + } + return $container; + }; + + ui.findElement = function ($container, viewport, cssSelector) { + if (viewport) { + return $container.find(viewport).find(cssSelector); + } + return $container.find(cssSelector); + }; + + ui.getUIElements = function (options, $el) { + var $container, result; + + if (options.instances.viewports) { + return options.instances.viewports; + } + + $container = ui.getContainer(options, $el); + + result = {}; + result.$progressbar = ui.findElement($container, options.ui.viewports.progress, "div.progress"); + if (options.ui.showVerdictsInsideProgressBar) { + result.$verdict = result.$progressbar.find("span.password-verdict"); + } + + if (!options.ui.showPopover) { + if (!options.ui.showVerdictsInsideProgressBar) { + result.$verdict = ui.findElement($container, options.ui.viewports.verdict, "span.password-verdict"); + } + result.$errors = ui.findElement($container, options.ui.viewports.errors, "ul.error-list"); + } + + options.instances.viewports = result; + return result; + }; + + ui.initProgressBar = function (options, $el) { + var $container = ui.getContainer(options, $el), + progressbar = "<div class='progress'><div class='"; + + if (!options.ui.bootstrap2) { + progressbar += "progress-"; + } + progressbar += "bar'>"; + if (options.ui.showVerdictsInsideProgressBar) { + progressbar += "<span class='password-verdict'></span>"; + } + progressbar += "</div></div>"; + + if (options.ui.viewports.progress) { + $container.find(options.ui.viewports.progress).append(progressbar); + } else { + $(progressbar).insertAfter($el); + } + }; + + ui.initHelper = function (options, $el, html, viewport) { + var $container = ui.getContainer(options, $el); + if (viewport) { + $container.find(viewport).append(html); + } else { + $(html).insertAfter($el); + } + }; + + ui.initVerdict = function (options, $el) { + ui.initHelper(options, $el, "<span class='password-verdict'></span>", + options.ui.viewports.verdict); + }; + + ui.initErrorList = function (options, $el) { + ui.initHelper(options, $el, "<ul class='error-list'></ul>", + options.ui.viewports.errors); + }; + + ui.initPopover = function (options, $el) { + $el.popover("destroy"); + $el.popover({ + html: true, + placement: "top", + trigger: "manual", + content: " " + }); + }; + + ui.initUI = function (options, $el) { + if (options.ui.showPopover) { + ui.initPopover(options, $el); + } else { + if (options.ui.showErrors) { ui.initErrorList(options, $el); } + if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) { + ui.initVerdict(options, $el); + } + } + if (options.ui.showProgressBar) { + ui.initProgressBar(options, $el); + } + }; + + ui.possibleProgressBarClasses = ["danger", "warning", "success"]; + + ui.updateProgressBar = function (options, $el, cssClass, percentage) { + var $progressbar = ui.getUIElements(options, $el).$progressbar, + $bar = $progressbar.find(".progress-bar"), + cssPrefix = "progress-"; + + if (options.ui.bootstrap2) { + $bar = $progressbar.find(".bar"); + cssPrefix = ""; + } + + $.each(ui.possibleProgressBarClasses, function (idx, value) { + $bar.removeClass(cssPrefix + "bar-" + value); + }); + $bar.addClass(cssPrefix + "bar-" + barClasses[cssClass]); + $bar.css("width", percentage + '%'); + }; + + ui.updateVerdict = function (options, $el, text) { + var $verdict = ui.getUIElements(options, $el).$verdict; + $verdict.text(text); + }; + + ui.updateErrors = function (options, $el) { + var $errors = ui.getUIElements(options, $el).$errors, + html = ""; + $.each(options.instances.errors, function (idx, err) { + html += "<li>" + err + "</li>"; + }); + $errors.html(html); + }; + + ui.updatePopover = function (options, $el, verdictText) { + var popover = $el.data("bs.popover"), + html = "", + hide = true; + + if (options.ui.showVerdicts && + !options.ui.showVerdictsInsideProgressBar && + verdictText.length > 0) { + html = "<h5><span class='password-verdict'>" + verdictText + + "</span></h5>"; + hide = false; + } + if (options.ui.showErrors) { + html += "<div><ul class='error-list' style='margin-bottom: 0; margin-left: -20px'>"; + $.each(options.instances.errors, function (idx, err) { + html += "<li>" + err + "</li>"; + hide = false; + }); + html += "</ul></div>"; + } + + if (hide) { + $el.popover("hide"); + return; + } + + if (options.ui.bootstrap2) { popover = $el.data("popover"); } + + if (popover.$arrow && popover.$arrow.parents("body").length > 0) { + $el.find("+ .popover .popover-content").html(html); + } else { + // It's hidden + popover.options.content = html; + $el.popover("show"); + } + }; + + ui.updateFieldStatus = function (options, $el, cssClass) { + var targetClass = options.ui.bootstrap2 ? ".control-group" : ".form-group", + $container = $el.parents(targetClass).first(); + + $.each(statusClasses, function (idx, css) { + if (!options.ui.bootstrap2) { css = "has-" + css; } + $container.removeClass(css); + }); + + cssClass = statusClasses[cssClass]; + if (!options.ui.bootstrap2) { cssClass = "has-" + cssClass; } + $container.addClass(cssClass); + }; + + ui.percentage = function (score, maximun) { + var result = Math.floor(100 * score / maximun); + result = result < 0 ? 0 : result; + result = result > 100 ? 100 : result; + return result; + }; + + ui.getVerdictAndCssClass = function (options, score) { + var cssClass, verdictText, level; + + if (score <= 0) { + cssClass = 0; + level = -1; + verdictText = options.ui.verdicts[0]; + } else if (score < options.ui.scores[0]) { + cssClass = 0; + level = 0; + verdictText = options.ui.verdicts[0]; + } else if (score < options.ui.scores[1]) { + cssClass = 0; + level = 1; + verdictText = options.ui.verdicts[1]; + } else if (score < options.ui.scores[2]) { + cssClass = 1; + level = 2; + verdictText = options.ui.verdicts[2]; + } else if (score < options.ui.scores[3]) { + cssClass = 1; + level = 3; + verdictText = options.ui.verdicts[3]; + } else { + cssClass = 2; + level = 4; + verdictText = options.ui.verdicts[4]; + } + + return [verdictText, cssClass, level]; + }; + + ui.updateUI = function (options, $el, score) { + var cssClass, barPercentage, verdictText; + + cssClass = ui.getVerdictAndCssClass(options, score); + verdictText = cssClass[0]; + cssClass = cssClass[1]; + + if (options.ui.showProgressBar) { + barPercentage = ui.percentage(score, options.ui.scores[3]); + ui.updateProgressBar(options, $el, cssClass, barPercentage); + if (options.ui.showVerdictsInsideProgressBar) { + ui.updateVerdict(options, $el, verdictText); + } + } + + if (options.ui.showStatus) { + ui.updateFieldStatus(options, $el, cssClass); + } + + if (options.ui.showPopover) { + ui.updatePopover(options, $el, verdictText); + } else { + if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) { + ui.updateVerdict(options, $el, verdictText); + } + if (options.ui.showErrors) { + ui.updateErrors(options, $el); + } + } + }; + }(jQuery, ui)); + +// Source: src/methods.js + + + + + var methods = {}; + + (function ($, methods) { + "use strict"; + var onKeyUp, applyToAll; + + onKeyUp = function (event) { + var $el = $(event.target), + options = $el.data("pwstrength-bootstrap"), + word = $el.val(), + userInputs, + verdictText, + verdictLevel, + score; + + if (options === undefined) { return; } + + options.instances.errors = []; + if (options.common.zxcvbn) { + userInputs = []; + $.each(options.common.userInputs, function (idx, selector) { + userInputs.push($(selector).val()); + }); + userInputs.push($(options.common.usernameField).val()); + score = zxcvbn(word, userInputs).entropy; + } else { + score = rulesEngine.executeRules(options, word); + } + ui.updateUI(options, $el, score); + verdictText = ui.getVerdictAndCssClass(options, score); + verdictLevel = verdictText[2]; + verdictText = verdictText[0]; + + if (options.common.debug) { console.log(score + ' - ' + verdictText); } + + if ($.isFunction(options.common.onKeyUp)) { + options.common.onKeyUp(event, { + score: score, + verdictText: verdictText, + verdictLevel: verdictLevel + }); + } + }; + + methods.init = function (settings) { + this.each(function (idx, el) { + // Make it deep extend (first param) so it extends too the + // rules and other inside objects + var clonedDefaults = $.extend(true, {}, defaultOptions), + localOptions = $.extend(true, clonedDefaults, settings), + $el = $(el); + + localOptions.instances = {}; + $el.data("pwstrength-bootstrap", localOptions); + $el.on("keyup", onKeyUp); + $el.on("change", onKeyUp); + $el.on("onpaste", onKeyUp); + + ui.initUI(localOptions, $el); + if ($.trim($el.val())) { // Not empty, calculate the strength + $el.trigger("keyup"); + } + + if ($.isFunction(localOptions.common.onLoad)) { + localOptions.common.onLoad(); + } + }); + + return this; + }; + + methods.destroy = function () { + this.each(function (idx, el) { + var $el = $(el), + options = $el.data("pwstrength-bootstrap"), + elements = ui.getUIElements(options, $el); + elements.$progressbar.remove(); + elements.$verdict.remove(); + elements.$errors.remove(); + $el.removeData("pwstrength-bootstrap"); + }); + }; + + methods.forceUpdate = function () { + this.each(function (idx, el) { + var event = { target: el }; + onKeyUp(event); + }); + }; + + methods.addRule = function (name, method, score, active) { + this.each(function (idx, el) { + var options = $(el).data("pwstrength-bootstrap"); + + options.rules.activated[name] = active; + options.rules.scores[name] = score; + options.rules.extra[name] = method; + }); + }; + + applyToAll = function (rule, prop, value) { + this.each(function (idx, el) { + $(el).data("pwstrength-bootstrap").rules[prop][rule] = value; + }); + }; + + methods.changeScore = function (rule, score) { + applyToAll.call(this, rule, "scores", score); + }; + + methods.ruleActive = function (rule, active) { + applyToAll.call(this, rule, "activated", active); + }; + + $.fn.pwstrength = function (method) { + var result; + + if (methods[method]) { + result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === "object" || !method) { + result = methods.init.apply(this, arguments); + } else { + $.error("Method " + method + " does not exist on jQuery.pwstrength-bootstrap"); + } + + return result; + }; + }(jQuery, methods)); +}(jQuery)); \ No newline at end of file