diff options
291 files changed, 3841 insertions, 1403 deletions
diff --git a/.gitignore b/.gitignore index 4f778371512..2c6b65b7b7d 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ public/assets/ .envrc dump.rdb tags +.gitlab_shell_secret diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000000..ac2cdeba013 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.1.3 diff --git a/CHANGELOG b/CHANGELOG index 857a5bc9234..3b0a351c86d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +v 7.5.0 + - API: Add support for Hipchat (Kevin Houdebert) + - Add time zone configuration on gitlab.yml (Sullivan Senechal) + v 7.4.0 - Refactored membership logic - Improve error reporting on users API (Julien Bianchi) @@ -12,12 +16,29 @@ v 7.4.0 - API: Add support for forking a project via the API (Bernhard Kaindl) - API: filter project issues by milestone (Julien Bianchi) - Fail harder in the backup script + - Changes to Slack service structure, only webhook url needed - Zen mode for wiki and milestones (Robert Schilling) - Move Emoji parsing to html-pipeline-gitlab (Robert Schilling) - Font Awesome 4.2 integration (Sullivan Senechal) - Add Pushover service integration (Sullivan Senechal) - Add select field type for services options (Sullivan Senechal) - Add cross-project references to the Markdown parser (Vinnie Okada) + - Add task lists to issue and merge request descriptions (Vinnie Okada) + - Snippets can be public, internal or private + - Improve danger zone: ask project path to confirm data-loss action + - Raise exception on forgery + - Show build coverage in Merge Requests (requires GitLab CI v5.1) + - New milestone and label links on issue edit form + - Improved repository graphs + - Improve event note display in dashboard and project activity views (Vinnie Okada) + - Add users sorting to admin area + - UI improvements + - 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 + - Faster rev list + - Fix branch removal v 7.3.2 - Fix creating new file via web editor diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d1e8270f46..d8d3c251080 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic ## Security vulnerability disclosure -Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. +Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. ## Closing policy for issues and merge requests @@ -22,7 +22,7 @@ Issues and merge requests should be in English and contain appropriate language ## Issue tracker -To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/). +To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://about.gitlab.com/subscription/) and [consulting services](http://about.gitlab.com/consultancy/) are available from [GitLab.com](http://about.gitlab.com/). The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue. @@ -92,6 +92,7 @@ For examples of feedback on merge requests please look at already [closed merge 1. The change is as small as possible (see the above paragraph for details) 1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code) +1. All tests have to pass, if you suspect a failing CI build is unrelated to your contribution ask for tests to be restarted. See [the CI setup document](http://doc.gitlab.com/ce/development/ci_setup.html) on who you can ask for test restart. 1. Initially contains a single commit (please use `git rebase -i` to squash commits) 1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server) 1. Does not break any existing functionality @@ -100,7 +101,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 38f77a65b30..7ec1d6db408 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.0.1 +2.1.0 diff --git a/Gemfile.lock b/Gemfile.lock index babb23ed606..0e82f14ca9d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -168,7 +168,7 @@ GEM multi_json gitlab-grack (2.0.0.pre) rack (~> 1.5.1) - gitlab-grit (2.6.11) + gitlab-grit (2.6.12) charlock_holmes (~> 0.6) diff-lcs (~> 1.1) mime-types (~> 1.15) @@ -241,9 +241,11 @@ GEM html-pipeline (1.11.0) activesupport (>= 2) nokogiri (~> 1.4) - html-pipeline-gitlab (0.1.0) - gitlab_emoji (~> 0.0.1.1) + html-pipeline-gitlab (0.1.5) + actionpack (~> 4) + gitlab_emoji (~> 0.0.1) html-pipeline (~> 1.11.0) + sanitize (~> 2.1) http_parser.rb (0.5.3) httparty (0.13.0) json (~> 1.8) diff --git a/PROCESS.md b/PROCESS.md index c3a787662f7..1dd28d6b670 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -18,7 +18,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co - Responds to merge requests the issue team mentions them in and monitors for new merge requests - Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.) - Mark merge requests 'ready-for-merge' when they meet the contribution guidelines -- Mention developer(s) based on the [list of members and their specialities](https://www.gitlab.com/core-team/) +- Mention developer(s) based on the [list of members and their specialities](https://about.gitlab.com/core-team/) - Closes merge requests with no feedback from the reporter for two weeks ## Priorities of the issue team @@ -30,7 +30,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co ## Mentioning people -The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://www.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person. +The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://about.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person. ## Workflow labels @@ -79,7 +79,7 @@ Thanks for the issue report but we only support issues for the latest stable ver ### Support requests and configuration questions -Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. +Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://about.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. ### Code format diff --git a/README.md b/README.md index c0461543f2a..2c0643cf598 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a ## Installation Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options. -Since a manual installation is a lot of work and error prone we strongly recommend fast and reliable Omnibus package installation (deb/rpm) on that page. +Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm). ## Third-party applications @@ -1 +1 @@ -7.4.0-pre +7.5.0.pre diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee index fdefbfb92bd..4f76d8ce486 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 a333eed87f2..bcb2e6df7c0 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 a1a4dc8e24f..ff0d0bb32b9 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -177,6 +177,13 @@ $ -> $(@).closest(".diff-file").find(".notes_holder").toggle() e.preventDefault() + $(document).on "click", '.js-confirm-danger', (e) -> + e.preventDefault() + btn = $(e.target) + text = btn.data("confirm-danger-message") + form = btn.closest("form") + new ConfirmDangerModal(form, text) + (($) -> # Disable an element and add the 'disabled' Bootstrap class $.fn.extend disable: -> diff --git a/app/assets/javascripts/behaviors/taskable.js.coffee b/app/assets/javascripts/behaviors/taskable.js.coffee new file mode 100644 index 00000000000..ddce71c1886 --- /dev/null +++ b/app/assets/javascripts/behaviors/taskable.js.coffee @@ -0,0 +1,21 @@ +window.updateTaskState = (taskableType) -> + objType = taskableType.data + isChecked = $(this).prop("checked") + if $(this).is(":checked") + stateEvent = "task_check" + else + stateEvent = "task_uncheck" + + taskableUrl = $("form.edit-" + objType).first().attr("action") + taskableNum = taskableUrl.match(/\d+$/) + taskNum = 0 + $("li.task-list-item input:checkbox").each( (index, e) => + if e == this + taskNum = index + 1 + ) + + $.ajax + type: "PATCH" + url: taskableUrl + data: objType + "[state_event]=" + stateEvent + + "&" + objType + "[task_num]=" + taskNum diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee index 9db919e5a62..a5f15f80c5c 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 5f53439ca4b..0566e239191 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 4db9116a9de..83e793863b6 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 607b85eb45c..9e5f49b1f69 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 784d7d20bb1..c183e78e513 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 new file mode 100644 index 00000000000..bb99edbd09e --- /dev/null +++ b/app/assets/javascripts/confirm_danger_modal.js.coffee @@ -0,0 +1,18 @@ +class @ConfirmDangerModal + constructor: (form, text) -> + @form = form + $('.js-confirm-text').text(text || '') + $('.js-confirm-danger-input').val('') + $('#modal-confirm-danger').modal('show') + project_path = $('.js-confirm-danger-match').text() + submit = $('.js-confirm-danger-submit') + submit.disable() + + $('.js-confirm-danger-input').on 'input', -> + if rstrip($(@).val()) is project_path + submit.enable() + else + submit.disable() + + $('.js-confirm-danger-submit').on 'click', => + @form.submit() diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee index c4a0ccd9c2a..6ef5a539b8f 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 dbe00c487dc..52b4208524f 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/flash.js.coffee b/app/assets/javascripts/flash.js.coffee index cf1a37eae3e..b39ab0c4475 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/groups.js.coffee b/app/assets/javascripts/groups.js.coffee index 4b1000f9a6a..90122044248 100644 --- a/app/assets/javascripts/groups.js.coffee +++ b/app/assets/javascripts/groups.js.coffee @@ -1,10 +1,8 @@ -class GroupMembers +class @GroupMembers constructor: -> $('li.group_member').bind 'ajax:success', -> $(this).fadeOut() -@GroupMembers = GroupMembers - $ -> # avatar $('.js-choose-group-avatar-button').bind "click", -> diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 36935a0a159..597b4695a6d 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", -> @@ -6,4 +6,12 @@ class Issue $(".issue-box .inline-update").on "change", "#issue_assignee_id", -> $(this).submit() -@Issue = Issue + if $("a.btn-close").length + $("li.task-list-item input:checkbox").prop("disabled", false) + + $(".task-list-item input:checkbox").on( + "click" + null + "issue" + updateTaskState + ) diff --git a/app/assets/javascripts/labels.js.coffee b/app/assets/javascripts/labels.js.coffee index d306ad64f5b..1bc8840f9ac 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 4c9f20ae6fa..46e06424e5a 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') @@ -17,6 +17,8 @@ class MergeRequest disableButtonIfEmptyField '#commit_message', '.accept_merge_request' + if $("a.btn-close").length + $("li.task-list-item input:checkbox").prop("disabled", false) # Local jQuery finder $: (selector) -> @@ -72,6 +74,13 @@ class MergeRequest this.$('.remove_source_branch_in_progress').hide() this.$('.remove_source_branch_widget.failed').show() + $(".task-list-item input:checkbox").on( + "click" + null + "merge_request" + updateTaskState + ) + activateTab: (action) -> this.$('.merge-request-tabs li').removeClass 'active' this.$('.tab-content').hide() @@ -96,14 +105,6 @@ class MergeRequest else $('.ci_widget.ci-error').show() - switch state - when "success" - $('.mr-state-widget').addClass("panel-success") - when "failed" - $('.mr-state-widget').addClass("panel-danger") - when "running", "pending" - $('.mr-state-widget').addClass("panel-warning") - showCiCoverage: (coverage) -> cov_html = $('<span>') cov_html.addClass('ci-coverage') @@ -131,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 ea01c318d4f..c42f31933d3 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/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index ba8d7a9a2f5..978f83dd442 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 b31eb9ac9de..65c149b7886 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/project.js.coffee b/app/assets/javascripts/project.js.coffee index f4a8a178e76..aba40742e5f 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -1,4 +1,4 @@ -class Project +class @Project constructor: -> $('.project-edit-container').on 'ajax:before', => $('.project-edit-container').hide() @@ -24,9 +24,6 @@ class Project else $('#project_issues_tracker_id').removeAttr('disabled') - -@Project = Project - $ -> # Git clone panel switcher scope = $ '.git-clone-holder' diff --git a/app/assets/javascripts/project_import.js.coffee b/app/assets/javascripts/project_import.js.coffee index 7cf44da99fe..6633564a079 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/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index e144dfa1d68..c1801365266 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 b129619696f..f36c71fd25e 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 ab785a54543..27f0fd31d50 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 834c7e5dab0..9952fa0b00a 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 index 5eaa8ad4ff9..32486f7da54 100644 --- a/app/assets/javascripts/team_members.js.coffee +++ b/app/assets/javascripts/team_members.js.coffee @@ -1,6 +1,4 @@ -class TeamMembers +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 4852e879b68..d428db5b422 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/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee index 17e790e5b7c..66757565d3a 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/generic/common.scss b/app/assets/stylesheets/generic/common.scss index 803219a2e86..cd2f4e45e3c 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -356,3 +356,6 @@ table { font-size: 42px; } +.task-status { + margin-left: 10px; +} diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss index 0486955d6e1..94149594e24 100644 --- a/app/assets/stylesheets/generic/issue_box.scss +++ b/app/assets/stylesheets/generic/issue_box.scss @@ -10,8 +10,7 @@ .issue-box { color: #555; margin:20px 0; - background: #f9f9f9; - border-top-left-radius: 5px; + background: $box_bg; @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); &.issue-box-closed { @@ -112,7 +111,6 @@ float: left; font-weight: bold; padding: 10px 15px; - border-top-left-radius: 5px; } .creator { diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss index d347ab2c2e4..2653bfbf831 100644 --- a/app/assets/stylesheets/generic/lists.scss +++ b/app/assets/stylesheets/generic/lists.scss @@ -122,3 +122,7 @@ ul.bordered-list { } } } + +li.task-list-item { + list-style-type: none; +} diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/gl_bootstrap.scss index 45044c5acb6..9c5e76ab8e2 100644 --- a/app/assets/stylesheets/gl_bootstrap.scss +++ b/app/assets/stylesheets/gl_bootstrap.scss @@ -233,8 +233,8 @@ $list-group-active-bg: $bg_primary; } .form-actions { - margin-bottom: 0; - background: #FFF; + margin: -15px; + margin-top: 18px; } } @@ -262,53 +262,33 @@ $list-group-active-bg: $bg_primary; } .panel-danger { - border-color: $border_danger; + @include panel-colored; .panel-heading { - color: #ffffff; - background-color: $bg_danger; + color: $border_danger; border-color: $border_danger; - a { - color: #FFF; - text-decoration: underline; - } } } .panel-success { - border-color: $border_success; + @include panel-colored; .panel-heading { - color: #ffffff; - background-color: $bg_success; + color: $border_success; border-color: $border_success; - a { - color: #FFF; - text-decoration: underline; - } } } .panel-primary { - border-color: $border_primary; + @include panel-colored; .panel-heading { - color: #ffffff; - background-color: $bg_primary; + color: $border_primary; border-color: $border_primary; - a { - color: #FFF; - text-decoration: underline; - } } } .panel-warning { - border-color: $border_warning; + @include panel-colored; .panel-heading { - color: #ffffff; - background-color: $bg_warning; + color: $border_warning; border-color: $border_warning; - a { - color: #FFF; - text-decoration: underline; - } } } diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/main/mixins.scss index 93faf5ced65..7f607fc4e8b 100644 --- a/app/assets/stylesheets/main/mixins.scss +++ b/app/assets/stylesheets/main/mixins.scss @@ -132,3 +132,14 @@ white-space: nowrap; max-width: $max_width; } + +@mixin panel-colored { + border: none; + background: $box_bg; + @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); + + .panel-heading { + font-weight: bold; + background-color: $box_bg; + } +} diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss index 72d84226fe7..c71984a5665 100644 --- a/app/assets/stylesheets/main/variables.scss +++ b/app/assets/stylesheets/main/variables.scss @@ -3,6 +3,7 @@ */ $style_color: #474D57; $hover: #FFECDB; +$box_bg: #F9F9F9; /* * Link colors diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index a7fa715d2e0..ebf8a6125c7 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -75,7 +75,7 @@ } .participants { - margin-bottom: 10px; + margin-bottom: 20px; } .issues_bulk_update { diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index acaad519778..ec844cc00b0 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -104,9 +104,54 @@ } .mr-state-widget { - .panel-body { + background: $box_bg; + margin-bottom: 20px; + @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); + + .ci_widget { + padding: 10px 15px; + font-size: 15px; + border-bottom: 1px solid #BBB; + color: #777; + background-color: #F5F5F5; + + &.ci-success { + color: $bg_success; + border-color: $border_success; + background-color: #F1FAF1; + } + + &.ci-pending { + color: #548; + border-color: #548; + background-color: #F4F1FA; + } + + &.ci-running { + color: $bg_warning; + border-color: $border_warning; + background-color: #FAF5F1; + } + + &.ci-failed { + color: $bg_danger; + border-color: $border_danger; + background-color: #FAF1F1; + } + + &.ci-error { + color: $bg_danger; + border-color: $border_danger; + background-color: #FAF1F1; + } + } + + .mr-widget-body { + padding: 10px 15px; + h4 { - margin-top: 0px; + font-size: 20px; + font-weight: normal; } p:last-child { @@ -114,6 +159,11 @@ } } + .mr-widget-footer { + padding: 10px 15px; + border-top: 1px solid #EEE; + } + .ci-coverage { float: right; } diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 2f0d344802f..7c2388e81be 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/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index f63df27eebd..baad9095b70 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -4,6 +4,7 @@ class Admin::UsersController < Admin::ApplicationController def index @users = User.filter(params[:filter]) @users = @users.search(params[:name]) if params[:name].present? + @users = @users.sort(@sort = params[:sort]) @users = @users.alphabetically.page(params[:page]) end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1a5215ca309..548d5e4d4c7 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,13 +7,12 @@ class ApplicationController < ActionController::Base 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? before_filter :require_email, unless: :devise_controller? - protect_from_forgery + protect_from_forgery with: :exception helper_method :abilities, :can? @@ -81,28 +80,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 @@ -119,14 +121,6 @@ class ApplicationController < ActionController::Base 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 +164,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/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 63c05d4f33b..ca88d033878 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -19,6 +19,7 @@ class Groups::GroupMembersController < ApplicationController def destroy @users_group = @group.group_members.find(params[:id]) + if can?(current_user, :destroy, @users_group) # May fail if last owner. @users_group.destroy respond_to do |format| diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 3ed6a69c2d8..bd4b310fcbf 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -15,15 +15,17 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController error.to_s.humanize if error end + # We only find ourselves here + # if the authentication to LDAP was successful. def ldap - # We only find ourselves here - # if the authentication to LDAP was successful. - @user = Gitlab::LDAP::User.find_or_create(oauth) - @user.remember_me = true if @user.persisted? + @user = Gitlab::LDAP::User.new(oauth) + @user.save if @user.changed? # will also save new users + gl_user = @user.gl_user + gl_user.remember_me = true if @user.persisted? # Do additional LDAP checks for the user filter and EE features - if Gitlab::LDAP::Access.allowed?(@user) - sign_in_and_redirect(@user) + if @user.allowed? + sign_in_and_redirect(gl_user) else flash[:alert] = "Access denied for your LDAP account." redirect_to new_user_session_path @@ -46,26 +48,28 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController current_user.save redirect_to profile_path else - @user = Gitlab::OAuth::User.find(oauth) + @user = Gitlab::OAuth::User.new(oauth) + @user.save - # Create user if does not exist - # and allow_single_sign_on is true - if Gitlab.config.omniauth['allow_single_sign_on'] && !@user - @user, errors = Gitlab::OAuth::User.create(oauth) - end - - if @user && !errors - sign_in_and_redirect(@user) + # Only allow properly saved users to login. + if @user.persisted? && @user.valid? + sign_in_and_redirect(@user.gl_user) else - if errors - error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ") - redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return - else - flash[:notice] = "There's no such user!" - end - redirect_to new_user_session_path + error_message = + if @user.gl_user.errors.any? + @user.gl_user.errors.map do |attribute, message| + "#{attribute} #{message}" + end.join(", ") + else + '' + end + + redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return end end + rescue StandardError + flash[:notice] = "There's no such user!" + redirect_to new_user_session_path end def oauth diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb index 5e305934433..56c306063c8 100644 --- a/app/controllers/projects/base_tree_controller.rb +++ b/app/controllers/projects/base_tree_controller.rb @@ -2,7 +2,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 a3c41301676..bad06e7aa2d 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -4,7 +4,7 @@ class Projects::BlameController < 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/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 7009e3b1bc8..04aa044001e 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -4,9 +4,9 @@ class Projects::BlobController < 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 :authorize_push!, only: [:destroy] + before_filter :authorize_push_code!, only: [:destroy] before_filter :blob @@ -20,7 +20,7 @@ class Projects::BlobController < Projects::ApplicationController flash[:notice] = "Your changes have been successfully committed" redirect_to project_tree_path(@project, @ref) else - flash[:alert] = result[:error] + flash[:alert] = result[:message] render :show end end diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index faa0ce67ca8..dd6df5d196b 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -3,8 +3,8 @@ class Projects::BranchesController < Projects::ApplicationController 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' diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 66c67b661db..cf05e6ea220 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -4,19 +4,19 @@ 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 +32,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 038645aa497..53a0d063d8e 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -5,7 +5,7 @@ class Projects::CommitsController < 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 @@ -17,7 +17,7 @@ class Projects::CommitsController < Projects::ApplicationController group(:commit_id).count respond_to do |format| - format.html # index.html.erb + format.html format.json { pager_json("projects/commits/_commits", @commits.size) } format.atom { render layout: false } end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 7a671e8455d..6d944025598 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -1,7 +1,7 @@ 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 d20937ea8ea..024b9520d30 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 8976d7c7be8..65661c80410 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 @@ -22,7 +22,7 @@ class Projects::EditTreeController < Projects::BaseTreeController redirect_to after_edit_path else - flash[:alert] = result[:error] + flash[:alert] = result[:message] render :show end end diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 610b4967fea..21d3970d65a 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -1,7 +1,7 @@ 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/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 9e7a55b23fd..c6d526f05c5 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -152,7 +152,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue_params params.require(:issue).permit( :title, :assignee_id, :position, :description, - :milestone_id, :state_event, label_ids: [] + :milestone_id, :state_event, :task_num, label_ids: [] ) end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index e13773d6465..20a733b10e1 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -250,7 +250,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController params.require(:merge_request).permit( :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, - :state_event, :description, label_ids: [] + :state_event, :description, :task_num, label_ids: [] ) end end diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb index 9832495c64f..009089ee639 100644 --- a/app/controllers/projects/network_controller.rb +++ b/app/controllers/projects/network_controller.rb @@ -4,7 +4,7 @@ class Projects::NetworkController < 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/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb index 71a5c6499ec..ffba706b2f6 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 5ec9c576a66..f4fdd616c50 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -4,7 +4,7 @@ class Projects::RawController < 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/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 7997c726fbb..9ac189a78b3 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -3,7 +3,7 @@ class Projects::RefsController < 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 switch diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 4e0f190ed1c..6d8ef0f1ac8 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -1,7 +1,7 @@ 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 4c558e137ea..b50f6286459 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -40,7 +40,7 @@ class Projects::ServicesController < Projects::ApplicationController def service_params params.require(:service).permit( :title, :token, :type, :active, :api_key, :subdomain, - :room, :recipients, :project_url, + :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound ) end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index cba058fe214..9d5dd8a95cc 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -17,7 +17,10 @@ class Projects::SnippetsController < Projects::ApplicationController respond_to :html def index - @snippets = @project.snippets.fresh.non_expired + @snippets = SnippetsFinder.new.execute(current_user, { + filter: :by_project, + project: @project + }) end def new @@ -88,6 +91,6 @@ class Projects::SnippetsController < Projects::ApplicationController end def snippet_params - params.require(:project_snippet).permit(:title, :content, :file_name, :private) + params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level) end end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 537c94bda20..94794fb5dd0 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -3,8 +3,8 @@ class Projects::TagsController < Projects::ApplicationController 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 7bb799eba64..0791e6080fb 100644 --- a/app/controllers/projects/team_members_controller.rb +++ b/app/controllers/projects/team_members_controller.rb @@ -10,7 +10,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 @@ -26,7 +26,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? @@ -36,7 +36,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| @@ -46,7 +46,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 b3380a6ff23..f81fc29677b 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -6,7 +6,6 @@ class ProjectsController < ApplicationController # Authorize before_filter :authorize_read_project!, except: [:index, :new, :create] before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :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] @@ -76,7 +75,7 @@ class ProjectsController < ApplicationController end def import - if project.import_finished? + if @project.import_finished? redirect_to @project return end @@ -98,7 +97,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 +147,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 +156,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/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 1bdba75c5e7..5ced98152a5 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -18,6 +18,10 @@ class SessionsController < Devise::SessionsController store_location_for(:redirect, redirect_path) end + if Gitlab.config.ldap.enabled + @ldap_servers = Gitlab::LDAP::Config.servers + end + super end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 3927584235e..30fb4c5552d 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -9,12 +9,14 @@ class SnippetsController < ApplicationController before_filter :set_title + skip_before_filter :authenticate_user!, only: [:index, :user_index] + respond_to :html - layout 'navless' + layout :determine_layout def index - @snippets = Snippet.are_public.fresh.non_expired.page(params[:page]).per(20) + @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(20) end def user_index @@ -22,22 +24,11 @@ class SnippetsController < ApplicationController render_404 and return unless @user - @snippets = @user.snippets.fresh.non_expired - - if @user == current_user - @snippets = case params[:scope] - when 'are_public' then - @snippets.are_public - when 'are_private' then - @snippets.are_private - else - @snippets - end - else - @snippets = @snippets.are_public - end - - @snippets = @snippets.page(params[:page]).per(20) + @snippets = SnippetsFinder.new.execute(current_user, { + filter: :by_user, + user: @user, + scope: params[:scope]}). + page(params[:page]).per(20) if @user == current_user render 'current_user_index' @@ -95,7 +86,14 @@ class SnippetsController < ApplicationController protected def snippet - @snippet ||= PersonalSnippet.where('author_id = :user_id or private is false', user_id: current_user.id).find(params[:id]) + @snippet ||= if current_user + PersonalSnippet.where("author_id = ? OR visibility_level IN (?)", + current_user.id, + [Snippet::PUBLIC, Snippet::INTERNAL]). + find(params[:id]) + else + PersonalSnippet.are_public.find(params[:id]) + end end def authorize_modify_snippet! @@ -111,6 +109,10 @@ class SnippetsController < ApplicationController end def snippet_params - params.require(:personal_snippet).permit(:title, :content, :file_name, :private) + params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level) + end + + def determine_layout + current_user ? 'navless' : 'public_users' end end diff --git a/app/finders/README.md b/app/finders/README.md index 47823c51efb..1f46518d230 100644 --- a/app/finders/README.md +++ b/app/finders/README.md @@ -1,7 +1,7 @@ # Finders -This type of classes responsible for collectiong items based on different conditions. -To prevent lookup methods in models like this: +This type of classes responsible for collection items based on different conditions. +To prevent lookup methods in models like this: ```ruby class Project @@ -13,10 +13,10 @@ end issues = project.issues_for_user_filtered_by(user, params) ``` -Better use this: +Better use this: ```ruby issues = IssuesFinder.new.execute(project, user, filter) ``` -It will help keep models thiner +It will help keep models thiner. diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb new file mode 100644 index 00000000000..b29ab6cf40b --- /dev/null +++ b/app/finders/snippets_finder.rb @@ -0,0 +1,61 @@ +class SnippetsFinder + def execute(current_user, params = {}) + filter = params[:filter] + + case filter + when :all then + snippets(current_user).fresh.non_expired + when :by_user then + by_user(current_user, params[:user], params[:scope]) + when :by_project + by_project(current_user, params[:project]) + end + end + + private + + def snippets(current_user) + if current_user + Snippet.public_and_internal + else + # Not authenticated + # + # Return only: + # public snippets + Snippet.are_public + end + end + + def by_user(current_user, user, scope) + snippets = user.snippets.fresh.non_expired + + if user == current_user + case scope + when 'are_internal' then + snippets.are_internal + when 'are_private' then + snippets.are_private + when 'are_public' then + snippets.are_public + else + snippets + end + else + snippets.public_and_internal + end + end + + def by_project(current_user, project) + snippets = project.snippets.fresh.non_expired + + if current_user + if project.team.member?(current_user.id) + snippets + else + snippets.public_and_internal + end + else + snippets.are_public + end + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 34d312b4100..021bd0a494c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -259,4 +259,16 @@ module ApplicationHelper super end + + def escaped_autolink(text) + auto_link ERB::Util.html_escape(text), link: :urls + end + + def promo_host + 'about.gitlab.com' + end + + def promo_url + 'https://' + promo_host + end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index cab2984a4c4..0e0532b65b2 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -120,4 +120,8 @@ module CommitsHelper class: 'commit-short-id') end end + + def truncate_sha(sha) + Commit.truncate_sha(sha) + end end diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index c4e33e3308f..acc0eeb76b3 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -37,40 +37,31 @@ module DashboardHelper end def assigned_entities_count(current_user, entity, scope = nil) - items = current_user.send("assigned_" + entity.pluralize).opened - - if scope.kind_of?(Group) - items = items.of_group(scope) - elsif scope.kind_of?(Project) - items = items.of_projects(scope) - end - - items.count + items = current_user.send('assigned_' + entity.pluralize) + get_count(items, scope) end def authored_entities_count(current_user, entity, scope = nil) - items = current_user.send(entity.pluralize).opened - - if scope.kind_of?(Group) - items = items.of_group(scope) - elsif scope.kind_of?(Project) - items = items.of_projects(scope) - end - - items.count + items = current_user.send(entity.pluralize) + get_count(items, scope) end def authorized_entities_count(current_user, entity, scope = nil) - items = entity.classify.constantize.opened + items = entity.classify.constantize + get_count(items, scope, true, current_user) + end + + protected + def get_count(items, scope, get_authorized = false, current_user = nil) + items = items.opened if scope.kind_of?(Group) items = items.of_group(scope) elsif scope.kind_of?(Project) items = items.of_projects(scope) - else + elsif get_authorized items = items.of_projects(current_user.authorized_projects) end - items.count end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 6aeab7bb8ce..71f97fbb8c8 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -136,9 +136,8 @@ module EventsHelper end def event_note(text) - text = first_line_in_markdown(text) - text = truncate(text, length: 150) - sanitize(markdown(text), tags: %w(a img b pre p)) + text = first_line_in_markdown(text, 150) + sanitize(text, tags: %w(a img b pre code p)) end def event_commit_title(message) diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 0365681a128..7d3cb749829 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -51,12 +51,14 @@ module GitlabMarkdownHelper @markdown.render(text).html_safe end - def first_line_in_markdown(text) - line = text.split("\n").detect do |i| - i.present? && markdown(i).present? - end - line += '...' unless line.nil? - line + # Return the first line of +text+, up to +max_chars+, after parsing the line + # as Markdown. HTML tags in the parsed output are not counted toward the + # +max_chars+ limit. If the length limit falls within a tag's contents, then + # the tag contents are truncated without removing the closing tag. + def first_line_in_markdown(text, max_chars = nil) + md = markdown(text).strip + + truncate_visible(md, max_chars || md.length) if md.present? end def render_wiki_content(wiki_page) @@ -204,4 +206,52 @@ module GitlabMarkdownHelper def correct_ref @ref ? @ref : "master" end + + private + + # Return +text+, truncated to +max_chars+ characters, excluding any HTML + # tags. + def truncate_visible(text, max_chars) + doc = Nokogiri::HTML.fragment(text) + content_length = 0 + truncated = false + + doc.traverse do |node| + if node.text? || node.content.empty? + if truncated + node.remove + next + end + + # Handle line breaks within a node + if node.content.strip.lines.length > 1 + node.content = "#{node.content.lines.first.chomp}..." + truncated = true + end + + num_remaining = max_chars - content_length + if node.content.length > num_remaining + node.content = node.content.truncate(num_remaining) + truncated = true + end + content_length += node.content.length + end + + truncated = truncate_if_block(node, truncated) + end + + doc.to_html + end + + # Used by #truncate_visible. If +node+ is the first block element, and the + # text hasn't already been truncated, then append "..." to the node contents + # and return true. Otherwise return false. + def truncate_if_block(node, truncated) + if node.element? && node.description.block? && !truncated + node.content = "#{node.content}..." if node.next_sibling + true + else + truncated + end + end end diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb index c0177dacbf8..7024483b8b3 100644 --- a/app/helpers/oauth_helper.rb +++ b/app/helpers/oauth_helper.rb @@ -1,6 +1,6 @@ module OauthHelper def ldap_enabled? - Devise.omniauth_providers.include?(:ldap) + Gitlab.config.ldap.enabled end def default_providers diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 6df7dae7314..883c1f63af6 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -56,6 +56,10 @@ module ProjectsHelper "You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?" end + def transfer_project_message(project) + "You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?" + end + def project_nav_tabs @nav_tabs ||= get_project_nav_tabs(@project, current_user) end @@ -128,9 +132,9 @@ module ProjectsHelper toggle_html = content_tag('span', class: 'toggle') do toggle_text = if starred - 'Unstar' + ' Unstar' else - 'Star' + ' Star' end content_tag('i', ' ', class: 'fa fa-star') + toggle_text diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index c3b537eac47..9c611a1c147 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -90,7 +90,7 @@ module TreeHelper end def editing_preview_title(filename) - if gitlab_markdown?(filename) || markup?(filename) + if Gitlab::MarkdownHelper.previewable?(filename) 'Preview' else 'Diff' diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index 8b83b8ff640..deb9c8b4d49 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -28,6 +28,23 @@ module VisibilityLevelHelper end end + def snippet_visibility_level_description(level) + capture_haml do + haml_tag :span do + case level + when Gitlab::VisibilityLevel::PRIVATE + haml_concat "The snippet is visible only for me" + when Gitlab::VisibilityLevel::INTERNAL + haml_concat "The snippet is visible for any logged in user." + when Gitlab::VisibilityLevel::PUBLIC + haml_concat "The snippet can be accessed" + haml_concat "without any" + haml_concat "authentication." + end + end + end + end + def visibility_level_icon(level) case level when Gitlab::VisibilityLevel::PRIVATE diff --git a/app/models/commit.rb b/app/models/commit.rb index a1343b65c72..212229649fc 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -19,13 +19,24 @@ class Commit class << self def decorate(commits) - commits.map { |c| self.new(c) } + commits.map do |commit| + if commit.kind_of?(Commit) + commit + else + self.new(commit) + end + end end # Calculate number of lines to render for diffs def diff_line_count(diffs) diffs.reduce(0) { |sum, d| sum + d.diff.lines.count } end + + # Truncate sha to 8 characters + def truncate_sha(sha) + sha[0..7] + end end attr_accessor :raw @@ -111,7 +122,7 @@ class Commit # Mentionable override. def gfm_reference - "commit #{sha[0..5]}" + "commit #{id}" end def method_missing(m, *args, &block) @@ -124,6 +135,11 @@ class Commit super end + # Truncate sha to 8 characters + def short_id + @raw.short_id(7) + end + def parents @parents ||= Commit.decorate(super) end diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb new file mode 100644 index 00000000000..410e8dc820b --- /dev/null +++ b/app/models/concerns/taskable.rb @@ -0,0 +1,51 @@ +# Contains functionality for objects that can have task lists in their +# descriptions. Task list items can be added with Markdown like "* [x] Fix +# bugs". +# +# Used by MergeRequest and Issue +module Taskable + TASK_PATTERN_MD = /^(?<bullet> *[*-] *)\[(?<checked>[ xX])\]/.freeze + TASK_PATTERN_HTML = /^<li>\[(?<checked>[ xX])\]/.freeze + + # Change the state of a task list item for this Taskable. Edit the object's + # description by finding the nth task item and changing its checkbox + # placeholder to "[x]" if +checked+ is true, or "[ ]" if it's false. + # Note: task numbering starts with 1 + def update_nth_task(n, checked) + index = 0 + check_char = checked ? 'x' : ' ' + + # Do this instead of using #gsub! so that ActiveRecord detects that a field + # has changed. + self.description = self.description.gsub(TASK_PATTERN_MD) do |match| + index += 1 + case index + when n then "#{$LAST_MATCH_INFO[:bullet]}[#{check_char}]" + else match + end + end + + save + end + + # Return true if this object's description has any task list items. + def tasks? + description && description.match(TASK_PATTERN_MD) + end + + # Return a string that describes the current state of this Taskable's task + # list items, e.g. "20 tasks (12 done, 8 unfinished)" + def task_status + return nil unless description + + num_tasks = 0 + num_done = 0 + + description.scan(TASK_PATTERN_MD) do + num_tasks += 1 + num_done += 1 unless $LAST_MATCH_INFO[:checked] == ' ' + end + + "#{num_tasks} tasks (#{num_done} done, #{num_tasks - num_done} unfinished)" + end +end diff --git a/app/models/event.rb b/app/models/event.rb index 9e296c00281..c0b126713a6 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -266,7 +266,7 @@ class Event < ActiveRecord::Base end def note_short_commit_id - note_commit_id[0..8] + Commit.truncate_sha(note_commit_id) end def note_commit? diff --git a/app/models/issue.rb b/app/models/issue.rb index 13152fdf94e..8a9e969248c 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -23,6 +23,7 @@ require 'file_size_validator' class Issue < ActiveRecord::Base include Issuable include InternalId + include Taskable ActsAsTaggableOn.strict_case_match = true diff --git a/app/models/member.rb b/app/models/member.rb index 7dc13c18bf3..671ef466baa 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -1,3 +1,18 @@ +# == Schema Information +# +# Table name: members +# +# id :integer not null, primary key +# access_level :integer not null +# source_id :integer not null +# source_type :string(255) not null +# user_id :integer not null +# notification_level :integer not null +# type :string(255) +# created_at :datetime +# updated_at :datetime +# + class Member < ActiveRecord::Base include Notifiable include Gitlab::Access diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index e72393c4278..b7f296b13fb 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -1,3 +1,18 @@ +# == Schema Information +# +# Table name: members +# +# id :integer not null, primary key +# access_level :integer not null +# source_id :integer not null +# source_type :string(255) not null +# user_id :integer not null +# notification_level :integer not null +# type :string(255) +# created_at :datetime +# updated_at :datetime +# + class GroupMember < Member SOURCE_TYPE = 'Namespace' diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 71525f91961..30c09f768d7 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -1,3 +1,18 @@ +# == Schema Information +# +# Table name: members +# +# id :integer not null, primary key +# access_level :integer not null +# source_id :integer not null +# source_type :string(255) not null +# user_id :integer not null +# notification_level :integer not null +# type :string(255) +# created_at :datetime +# updated_at :datetime +# + class ProjectMember < Member SOURCE_TYPE = 'Project' diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e0358c1889c..7c525b02f48 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -25,6 +25,7 @@ require Rails.root.join("lib/static_model") class MergeRequest < ActiveRecord::Base include Issuable + include Taskable include InternalId belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 409e82ed1ef..a71122d5e07 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -55,7 +55,7 @@ class MergeRequestDiff < ActiveRecord::Base end def last_commit_short_sha - @last_commit_short_sha ||= last_commit.sha[0..10] + @last_commit_short_sha ||= last_commit.short_id end private diff --git a/app/models/note.rb b/app/models/note.rb index 0c1d792ca9a..f0ed7580b4c 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -47,7 +47,7 @@ class Note < ActiveRecord::Base scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) } scope :inline, ->{ where("line_code IS NOT NULL") } scope :not_inline, ->{ where(line_code: [nil, '']) } - + scope :system, ->{ where(system: true) } scope :common, ->{ where(noteable_type: ["", nil]) } scope :fresh, ->{ order("created_at ASC, id ASC") } scope :inc_author_project, ->{ includes(:project, :author) } @@ -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 } @@ -168,17 +168,30 @@ class Note < ActiveRecord::Base # Determine whether or not a cross-reference note already exists. def cross_reference_exists?(noteable, mentioner) gfm_reference = mentioner_gfm_ref(noteable, mentioner) - - where(['noteable_id = ? and system = ? and note like ?', - noteable.id, true, "_mentioned in #{gfm_reference}_"]).any? + notes = if noteable.is_a?(Commit) + where(commit_id: noteable.id) + else + where(noteable_id: noteable.id) + end + + notes.where('note like ?', cross_reference_note_content(gfm_reference)). + system.any? end def search(query) 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. @@ -244,6 +257,10 @@ class Note < ActiveRecord::Base 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? diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb index a3c0d201ee5..9cee3b70cb3 100644 --- a/app/models/personal_snippet.rb +++ b/app/models/personal_snippet.rb @@ -2,17 +2,17 @@ # # Table name: snippets # -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# id :integer not null, primary key +# title :string(255) +# content :text +# author_id :integer not null +# project_id :integer +# created_at :datetime +# updated_at :datetime +# file_name :string(255) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # class PersonalSnippet < Snippet diff --git a/app/models/project.rb b/app/models/project.rb index 44d63d37bee..613f98ba44b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -64,6 +64,7 @@ class Project < ActiveRecord::Base has_one :assembla_service, dependent: :destroy has_one :gemnasium_service, dependent: :destroy has_one :slack_service, dependent: :destroy + has_one :buildbox_service, dependent: :destroy has_one :pushover_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link @@ -172,7 +173,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 @@ -312,7 +313,7 @@ class Project < ActiveRecord::Base end def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover) + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox) end def gitlab_ci? diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index 3421a0330aa..0b90a14f39c 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class AssemblaService < Service diff --git a/app/models/project_services/buildbox_service.rb b/app/models/project_services/buildbox_service.rb new file mode 100644 index 00000000000..b0f8e28c97f --- /dev/null +++ b/app/models/project_services/buildbox_service.rb @@ -0,0 +1,121 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +class BuildboxService < CiService + prop_accessor :project_url, :token + + validates :project_url, presence: true, if: :activated? + validates :token, presence: true, if: :activated? + + after_save :compose_service_hook, if: :activated? + + def webhook_url + "#{buildbox_endpoint('webhook')}/deliver/#{webhook_token}" + end + + def compose_service_hook + hook = service_hook || build_service_hook + hook.url = webhook_url + hook.save + end + + def execute(data) + service_hook.execute(data) + end + + def commit_status(sha) + response = HTTParty.get(commit_status_path(sha), verify: false) + + if response.code == 200 && response['status'] + response['status'] + else + :error + end + end + + def commit_status_path(sha) + "#{buildbox_endpoint('gitlab')}/status/#{status_token}.json?commit=#{sha}" + end + + def build_page(sha) + "#{project_url}/builds?commit=#{sha}" + end + + def builds_path + "#{project_url}/builds?branch=#{project.default_branch}" + end + + def status_img_path + "#{buildbox_endpoint('badge')}/#{status_token}.svg" + end + + def title + 'Buildbox' + end + + def description + 'Continuous integration and deployments' + end + + def to_param + 'buildbox' + end + + def fields + [ + { type: 'text', + name: 'token', + placeholder: 'Buildbox project GitLab token' }, + + { type: 'text', + name: 'project_url', + placeholder: 'https://buildbox.io/example/project' } + ] + end + + private + + def webhook_token + token_parts.first + end + + def status_token + token_parts.second + end + + def token_parts + if token.present? + token.split(':') + else + [] + end + end + + def buildbox_endpoint(subdomain = nil) + endpoint = 'https://buildbox.io' + + if subdomain.present? + uri = Addressable::URI.parse(endpoint) + new_endpoint = "#{uri.scheme || 'http'}://#{subdomain}.#{uri.host}" + + if uri.port.present? + "#{new_endpoint}:#{uri.port}" + else + new_endpoint + end + else + endpoint + end + end +end diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index 2d8950db491..0736ddab99b 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class CampfireService < Service diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index 829f495abc6..b1d5e49ede3 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # # Base class for CI services diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index 5c4537cfca5..b9071b98295 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class EmailsOnPushService < Service diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 4d11b00c192..0020b4482e5 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require "flowdock-git-hook" diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 7b6c87e4cec..6d2fc06a5d0 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require "gemnasium/gitlab_service" diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 001b11c5966..a897c4ab76b 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# property :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class GitlabCiService < CiService diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 3a1ba168e6a..4078938cdbb 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class HipchatService < Service diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index 3aa928b92a0..09e114f9cca 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class PivotaltrackerService < Service diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 4bda93f6006..837002ef3c8 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -2,21 +2,19 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class SlackService < Service - prop_accessor :room, :subdomain, :token - validates :room, presence: true, if: :activated? - validates :subdomain, presence: true, if: :activated? - validates :token, presence: true, if: :activated? + prop_accessor :webhook + validates :webhook, presence: true, if: :activated? def title 'Slack' @@ -32,9 +30,7 @@ class SlackService < Service def fields [ - { type: 'text', name: 'subdomain', placeholder: '' }, - { type: 'text', name: 'token', placeholder: '' }, - { type: 'text', name: 'room', placeholder: 'Ex. #general' }, + { type: 'text', name: 'webhook', placeholder: '' } ] end @@ -44,10 +40,14 @@ class SlackService < Service project_name: project_name )) - notifier = Slack::Notifier.new(subdomain, token) - notifier.channel = room - notifier.username = 'GitLab' - notifier.ping(message.pretext, attachments: message.attachments) + 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 end private diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb index 14c88046423..9e2c1b0e18e 100644 --- a/app/models/project_snippet.rb +++ b/app/models/project_snippet.rb @@ -2,17 +2,17 @@ # # Table name: snippets # -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# id :integer not null, primary key +# title :string(255) +# content :text +# author_id :integer not null +# project_id :integer +# created_at :datetime +# updated_at :datetime +# file_name :string(255) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # class ProjectSnippet < Snippet diff --git a/app/models/project_team.rb b/app/models/project_team.rb index e065554d3b8..657ee23ae23 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -133,6 +133,10 @@ class ProjectTeam max_tm_access(user.id) == Gitlab::Access::MASTER end + def member?(user_id) + !!find_tm(user_id) + end + def max_tm_access(user_id) access = [] access << project.project_members.find_by(user_id: user_id).try(:access_field) diff --git a/app/models/repository.rb b/app/models/repository.rb index 339e485e6d2..93994123a90 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -30,6 +30,8 @@ class Repository commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Commit.new(commit) if commit commit + rescue Rugged::OdbError => ex + nil end def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false) diff --git a/app/models/service.rb b/app/models/service.rb index 1f3a6520473..c489c1e96e1 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -2,14 +2,15 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# # To add new service you should build a class inherited from Service # and implement a set of methods diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 80c1af8f337..a47fbca3260 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -2,23 +2,24 @@ # # Table name: snippets # -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# id :integer not null, primary key +# title :string(255) +# content :text +# author_id :integer not null +# project_id :integer +# created_at :datetime +# updated_at :datetime +# file_name :string(255) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # class Snippet < ActiveRecord::Base include Linguist::BlobHelper + include Gitlab::VisibilityLevel - default_value_for :private, true + default_value_for :visibility_level, Snippet::PRIVATE belongs_to :author, class_name: "User" @@ -30,10 +31,13 @@ class Snippet < ActiveRecord::Base validates :title, presence: true, length: { within: 0..255 } validates :file_name, presence: true, length: { within: 0..255 } validates :content, presence: true + validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } # Scopes - scope :are_public, -> { where(private: false) } - scope :are_private, -> { where(private: true) } + scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) } + scope :are_private, -> { where(visibility_level: Snippet::PRIVATE) } + scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) } + scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) } scope :fresh, -> { order("created_at DESC") } scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } @@ -66,6 +70,10 @@ class Snippet < ActiveRecord::Base expires_at && expires_at < Time.current end + def visibility_level_field + visibility_level + end + class << self def search(query) where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%") @@ -76,7 +84,7 @@ class Snippet < ActiveRecord::Base end def accessible_to(user) - where('private = ? OR author_id = ?', false, user) + where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user) end end end diff --git a/app/models/tree.rb b/app/models/tree.rb index 07c9a825e24..4f5d81f0a5e 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -15,7 +15,7 @@ class Tree # by markup renderer. if available_readmes.length > 1 supported_readmes = available_readmes.select do |readme| - gitlab_markdown?(readme.name) || markup?(readme.name) + previewable?(readme.name) end # Take the first supported readme, or the first available readme, if we diff --git a/app/models/user.rb b/app/models/user.rb index c90f2462426..42faea0070e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -178,8 +178,7 @@ class User < ActiveRecord::Base scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } - scope :ldap, -> { where(provider: 'ldap') } - + scope :ldap, -> { where('provider LIKE ?', 'ldap%') } scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active } # @@ -196,6 +195,16 @@ class User < ActiveRecord::Base end end + def sort(method) + case method.to_s + when 'recent_sign_in' then reorder('users.last_sign_in_at DESC') + when 'oldest_sign_in' then reorder('users.last_sign_in_at ASC') + when 'recently_created' then reorder('users.created_at DESC') + when 'late_created' then reorder('users.created_at ASC') + else reorder("users.name ASC") + end + end + def find_for_commit(email, name) # Prefer email match over name match User.where(email: email).first || @@ -397,7 +406,7 @@ class User < ActiveRecord::Base end def ldap_user? - extern_uid && provider == 'ldap' + extern_uid && provider.start_with?('ldap') end def accessible_deploy_keys diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index db6f0831f8b..bd245100955 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -10,12 +10,6 @@ module Files private - def success - out = super() - out[:error] = '' - out - end - def repository project.repository end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb new file mode 100644 index 00000000000..e3371ec3c1b --- /dev/null +++ b/app/services/issuable_base_service.rb @@ -0,0 +1,13 @@ +class IssuableBaseService < BaseService + private + + def create_assignee_note(issuable) + Note.create_assignee_change_note( + issuable, issuable.project, current_user, issuable.assignee) + end + + def create_milestone_note(issuable) + Note.create_milestone_change_note( + issuable, issuable.project, current_user, issuable.milestone) + end +end diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 71b9ffc3489..41948f226a6 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -1,21 +1,13 @@ module Issues - class BaseService < ::BaseService + class BaseService < ::IssuableBaseService private - def create_assignee_note(issue) - Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee) - end - def execute_hooks(issue, action = 'open') issue_data = issue.to_hook_data 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) end - - def create_milestone_note(issue) - Note.create_milestone_change_note(issue, issue.project, current_user, issue.milestone) - end end end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index a0e57144435..0ee9635ed99 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -8,9 +8,14 @@ module Issues Issues::ReopenService.new(project, current_user, {}).execute(issue) when 'close' Issues::CloseService.new(project, current_user, {}).execute(issue) + when 'task_check' + issue.update_nth_task(params[:task_num].to_i, true) + when 'task_uncheck' + issue.update_nth_task(params[:task_num].to_i, false) end - if params.present? && issue.update_attributes(params.except(:state_event)) + if params.present? && issue.update_attributes(params.except(:state_event, + :task_num)) issue.reset_events_cache if issue.previous_changes.include?('milestone_id') diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 2907f3587da..694994001b0 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -1,11 +1,5 @@ module MergeRequests - class BaseService < ::BaseService - - private - - def create_assignee_note(merge_request) - Note.create_assignee_change_note(merge_request, merge_request.project, current_user, merge_request.assignee) - end + class BaseService < ::IssuableBaseService def create_note(merge_request) Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil) @@ -16,9 +10,5 @@ module MergeRequests merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) end end - - def create_milestone_note(merge_request) - Note.create_milestone_change_note(merge_request, merge_request.project, current_user, merge_request.milestone) - end end end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 6e416a0080c..fc26619cd17 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -17,9 +17,15 @@ module MergeRequests MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request) when 'close' MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request) + when 'task_check' + merge_request.update_nth_task(params[:task_num].to_i, true) + when 'task_uncheck' + merge_request.update_nth_task(params[:task_num].to_i, false) end - if params.present? && merge_request.update_attributes(params.except(:state_event)) + if params.present? && merge_request.update_attributes( + params.except(:state_event, :task_num) + ) merge_request.reset_events_cache if merge_request.previous_changes.include?('milestone_id') diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index fe39f83b400..36781314278 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -119,7 +119,7 @@ class NotificationService # ignore gitlab service messages return true if note.note =~ /\A_Status changed to closed_/ - return true if note.note =~ /\A_mentioned in / && note.system == true + return true if note.cross_reference? && note.system == true opts = { noteable_type: note.noteable_type, project_id: note.project_id } diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index c56863ce274..f4d7e25fd74 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/show.html.haml b/app/views/admin/groups/show.html.haml index c1a9214b77a..4494acc4842 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -74,13 +74,13 @@ %ul.well-list.group-users-list - @members.each do |member| - user = member.user - %li{class: dom_class(user)} + %li{class: dom_class(member), id: dom_id(user)} .list-item-name %strong = link_to user.name, admin_user_path(user) %span.pull-right.light = member.human_access - = link_to group_group_members_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do %i.fa.fa-minus.fa-inverse .panel-footer = paginate @members, param_name: 'members_page', theme: 'gitlab' diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index b3f8f012f00..384c6ee9af5 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/users/index.html.haml b/app/views/admin/users/index.html.haml index 5c2664e14fe..92c619738a2 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -32,6 +32,26 @@ .panel-heading Users (#{@users.total_count}) .panel-head-actions + .dropdown.inline + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %span.light sort: + - if @sort.present? + = @sort.humanize + - else + Name + %b.caret + %ul.dropdown-menu + %li + = link_to admin_users_path(sort: nil) do + Name + = link_to admin_users_path(sort: 'recent_sign_in') do + Recent sign in + = link_to admin_users_path(sort: 'oldest_sign_in') do + Oldest sign in + = link_to admin_users_path(sort: 'recently_created') do + Recently created + = link_to admin_users_path(sort: 'late_created') do + Late created = link_to 'New User', new_admin_user_path, class: "btn btn-new" %ul.well-list - @users.each do |user| diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml index 711e607f0bc..5d133cd8285 100644 --- a/app/views/dashboard/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/_zero_authorized_projects.html.haml @@ -46,5 +46,5 @@ %br Public projects are an easy way to allow everyone to have read-only access. .link_holder - = link_to explore_projects_path, class: "btn btn-new" do + = link_to trending_explore_projects_path, class: "btn btn-new" do Browse public projects » diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 6c5a878e904..01584611493 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,4 +1,4 @@ -= form_tag(user_omniauth_callback_path(:ldap), id: 'new_ldap_user' ) do += form_tag(user_omniauth_callback_path(provider), id: 'new_ldap_user' ) do = 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/ diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index b70b0d66172..ca7e9570b43 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -2,22 +2,24 @@ .login-heading %h3 Sign in .login-body - - if ldap_enabled? && gitlab_config.signin_enabled + - if ldap_enabled? %ul.nav.nav-tabs - %li.active - = link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab' - %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' + - @ldap_servers.each_with_index do |server, i| + %li{class: (:active if i.zero?)} + = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if gitlab_config.signin_enabled + %li + = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' .tab-content - %div#tab-ldap.tab-pane.active - = render partial: 'devise/sessions/new_ldap' - %div#tab-signin.tab-pane - = render partial: 'devise/sessions/new_base' + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)} + = render 'devise/sessions/new_ldap', provider: server['provider_name'] + - if gitlab_config.signin_enabled + %div#tab-signin.tab-pane + = render 'devise/sessions/new_base' - - elsif ldap_enabled? - = render partial: 'devise/sessions/new_ldap' - elsif gitlab_config.signin_enabled - = render partial: 'devise/sessions/new_base' + = render 'devise/sessions/new_base' - else %div No authentication methods configured. @@ -36,7 +38,6 @@ %span.light Did not receive confirmation email? = link_to "Send again", new_confirmation_path(resource_name) - - if extra_config.has_key?('sign_in_text') %hr = markdown(extra_config.sign_in_text) diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml index 0e03e116e7d..f0c34def145 100644 --- a/app/views/events/_commit.html.haml +++ b/app/views/events/_commit.html.haml @@ -1,5 +1,5 @@ %li.commit .commit-row-title - = link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id", alt: '' + = link_to truncate_sha(commit[:id]), project_commit_path(project, commit[:id]), class: "commit_short_id", alt: '' = gfm event_commit_title(commit[:message]), project diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml index 17228c430ca..2b63519edac 100644 --- a/app/views/events/_event_push.atom.haml +++ b/app/views/events/_event_push.atom.haml @@ -2,7 +2,7 @@ - event.commits.first(15).each do |commit| %p %strong= commit[:author][:name] - = link_to "(##{commit[:id][0...8]})", project_commit_path(event.project, id: commit[:id]) + = link_to "(##{truncate_sha(commit[:id])})", project_commit_path(event.project, id: commit[:id]) %i at = commit[:timestamp].to_time.to_s(:short) diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index 1bca64c7d50..b912b5e092f 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -22,4 +22,4 @@ - if event.commits_count > 2 %span ... and #{event.commits_count - 2} more commits. = link_to project_compare_path(event.project, from: event.commit_from, to: event.commit_to) do - %strong Compare → #{event.commit_from[0..7]}...#{event.commit_to[0..7]} + %strong Compare → #{truncate_sha(event.commit_from)}...#{truncate_sha(event.commit_to)} diff --git a/app/views/explore/projects/_project.html.haml b/app/views/explore/projects/_project.html.haml index 4bc79d0a8c7..ffbddbae4d6 100644 --- a/app/views/explore/projects/_project.html.haml +++ b/app/views/explore/projects/_project.html.haml @@ -6,6 +6,7 @@ - if current_page?(starred_explore_projects_path) %strong.pull-right + %i.fa.fa-star = pluralize project.star_count, 'star' .project-info diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index d4b11405517..420f0693756 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -1,6 +1,6 @@ .explore-trending-block %p.lead - %i.fa.fa-comments-o + %i.fa.fa-star See most starred projects %hr .public-projects diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 0b15affe785..eb24fd65d9e 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -11,16 +11,7 @@ - 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" - - .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 .col-sm-2 @@ -31,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/new.html.haml b/app/views/groups/new.html.haml index 235e299343a..6e17cdaef6f 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/groups/show.html.haml b/app/views/groups/show.html.haml index 4f4fc537d34..d876e87852c 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -24,7 +24,7 @@ = @group.name - if @group.description.present? %p - = auto_link @group.description, link: :urls + = escaped_autolink(@group.description) = render "projects", projects: @projects - if current_user .prepend-top-20 diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 903e093e5fc..7b8193abfdf 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -14,7 +14,7 @@ %br Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. %br - Read more about GitLab at #{link_to "www.gitlab.com", "https://www.gitlab.com/", target: "_blank"}. + Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}. %hr @@ -34,7 +34,7 @@ %ul.well-list %li See our website for - = link_to "getting help", "https://www.gitlab.com/getting-help/" + = link_to 'getting help', promo_url + '/getting-help/' %li Use the = link_to 'search bar', '#', onclick: 'Shortcuts.focusSearch(event)' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index f84de4430cc..a044fad8fa3 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -31,12 +31,12 @@ .clearfix %hr - %p - You can also specify notification level per group or per project - %br - By default all projects and groups uses notification level set above .row.all-notifications .col-md-6 + %p + You can also specify notification level per group or per project. + %br + By default all projects and groups uses notification level set above. %h4 Groups: %ul.bordered-list - @group_members.each do |users_group| @@ -44,6 +44,10 @@ = render 'settings', type: 'group', membership: users_group, notification: notification .col-md-6 + %p + To specify notification level per project of a group you belong to, + %br + you need to be a member of the project itself, not only its group. %h4 Projects: %ul.bordered-list - @project_members.each do |project_member| diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml new file mode 100644 index 00000000000..fd8320adb8d --- /dev/null +++ b/app/views/projects/_commit_button.html.haml @@ -0,0 +1,9 @@ +.form-actions + .commit-button-annotation + = button_tag 'Commit Changes', + class: 'btn commit-btn js-commit-button btn-create' + .message + to branch + %strong= ref + = link_to 'Cancel', cancel_path, + class: 'btn btn-cancel', data: {confirm: leave_edit_message} diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index cdbdec698fa..672a91e0eef 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -3,7 +3,7 @@ .project-home-row .project-home-desc - if @project.description.present? - = auto_link ERB::Util.html_escape(@project.description), link: :urls + = escaped_autolink(@project.description) - if can?(current_user, :admin_project, @project) – = link_to 'Edit', edit_project_path diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index e5cde488c3c..bdf02c6285d 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -15,7 +15,7 @@ %tr %td.blame-commit %span.commit - = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" + = link_to commit.short_id, project_commit_path(@project, commit), class: "commit_short_id" = commit_author_link(commit, avatar: true, size: 16) diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 0b6b6af4f90..e149f017f84 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -35,7 +35,7 @@ .commit-info-row %span.cgray= pluralize(@commit.parents.count, "parent") - @commit.parents.each do |parent| - = link_to parent.id[0...10], project_commit_path(@project, parent) + = link_to parent.short_id, project_commit_path(@project, parent) - if @branches.any? .commit-info-row diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 68852ba973f..1eb17f760dc 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -1,6 +1,6 @@ %li.commit.js-toggle-container .commit-row-title - = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" + = link_to commit.short_id, project_commit_path(project, commit), class: "commit_short_id" %span.str-truncated = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml index b36369b4285..574599aa2d2 100644 --- a/app/views/projects/commits/_inline_commit.html.haml +++ b/app/views/projects/commits/_inline_commit.html.haml @@ -1,6 +1,6 @@ %li.commit.inline-commit .commit-row-title - = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" + = link_to commit.short_id, project_commit_path(project, commit), class: "commit_short_id" %span.str-truncated = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 5ee5641b069..f48f4bb2953 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -86,101 +86,92 @@ - .danger-settings.js-toggle-container - .centered-light-block - %h3 - %i.fa.fa-exclamation-triangle - Dangerous settings - - %p Project settings below may result in data loss! - = link_to '#', class: 'btn js-toggle-button' do - Show them to me - %i.fa.fa-chevron-down - - .js-toggle-content.hide - - if can? current_user, :archive_project, @project - .panel.panel-default.panel.panel-warning + .danger-settings + - if can? current_user, :archive_project, @project + - if @project.archived? + .panel.panel-success .panel-heading - - if @project.archived? - Unarchive project - - else - Archive project + Unarchive project .panel-body - - if @project.archived? - %p - Unarchiving the project will mark its repository as active. - %br - The project can be committed to. - %br - %strong Once active this project shows up in the search and on the dashboard. - = link_to 'Unarchive', unarchive_project_path(@project), - data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." }, - method: :post, class: "btn btn-remove" - - else - %p - Archiving the project will mark its repository as read-only. - %br - It is hidden from the dashboard and doesn't show up in searches. - %br - %strong Archived projects cannot be committed to! - = link_to 'Archive', archive_project_path(@project), - data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." }, - method: :post, class: "btn btn-warning" + %p + Unarchiving the project will mark its repository as active. + %br + The project can be committed to. + %br + %strong Once active this project shows up in the search and on the dashboard. + = link_to 'Unarchive', unarchive_project_path(@project), + data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." }, + method: :post, class: "btn btn-success" - else - .nothing-here-block Only the project owner can archive a project - - .panel.panel-default.panel.panel-warning - .panel-heading Rename repository + .panel.panel-warning + .panel-heading + Archive project + .panel-body + %p + Archiving the project will mark its repository as read-only. + %br + It is hidden from the dashboard and doesn't show up in searches. + %br + %strong Archived projects cannot be committed to! + = link_to 'Archive', archive_project_path(@project), + data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." }, + method: :post, class: "btn btn-warning" + - else + .nothing-here-block Only the project owner can archive a project + + .panel.panel-default.panel.panel-warning + .panel-heading Rename repository + .errors-holder + .panel-body + = form_for(@project, html: { class: 'form-horizontal' }) do |f| + .form-group + = f.label :path, class: 'control-label' do + %span Path + .col-sm-9 + .form-group + .input-group + = f.text_field :path, class: 'form-control' + %span.input-group-addon .git + %ul + %li Be careful. Renaming a project's repository can have unintended side effects. + %li You will need to update your local repositories to point to the new location. + .form-actions + = f.submit 'Rename', class: "btn btn-warning" + + - if can?(current_user, :change_namespace, @project) + .panel.panel-default.panel.panel-danger + .panel-heading Transfer project .errors-holder .panel-body - = form_for(@project, html: { class: 'form-horizontal' }) do |f| + = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f| .form-group - = f.label :path, class: 'control-label' do - %span Path - .col-sm-9 + = f.label :namespace_id, class: 'control-label' do + %span Namespace + .col-sm-10 .form-group - .input-group - = f.text_field :path, class: 'form-control' - %span.input-group-addon .git + = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' } %ul - %li Be careful. Renaming a project's repository can have unintended side effects. + %li Be careful. Changing the project's namespace can have unintended side effects. + %li You can only transfer the project to namespaces you manage. %li You will need to update your local repositories to point to the new location. .form-actions - = f.submit 'Rename', class: "btn btn-warning" - - - if can?(current_user, :change_namespace, @project) - .panel.panel-default.panel.panel-danger - .panel-heading Transfer project - .errors-holder - .panel-body - = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f| - .form-group - = f.label :namespace_id, class: 'control-label' do - %span Namespace - .col-sm-10 - .form-group - = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' } - %ul - %li Be careful. Changing the project's namespace can have unintended side effects. - %li You can only transfer the project to namespaces you manage. - %li You will need to update your local repositories to point to the new location. - .form-actions - = f.submit 'Transfer', class: "btn btn-remove" - - else - .nothing-here-block Only the project owner can transfer a project + = f.submit 'Transfer', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) } + - else + .nothing-here-block Only the project owner can transfer a project - - if can?(current_user, :remove_project, @project) - .panel.panel-default.panel.panel-danger - .panel-heading Remove project - .panel-body + - if can?(current_user, :remove_project, @project) + .panel.panel-default.panel.panel-danger + .panel-heading Remove project + .panel-body + = form_tag(project_path(@project), method: :delete, html: { class: 'form-horizontal'}) do %p Removing the project will delete its repository and all related resources including issues, merge requests etc. %br %strong Removed projects cannot be restored! - = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove" - - else - .nothing-here-block Only project owner can remove a project + = link_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) } + - else + .nothing-here-block Only project owner can remove a project .save-project-loader.hide .center @@ -188,3 +179,6 @@ %i.fa.fa-spinner.fa-spin Saving project. %p Please wait a moment, this page will automatically refresh when ready. + + += render 'shared/confirm_modal', phrase: @project.path diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml index a863f7420a8..5ccde05063e 100644 --- a/app/views/projects/edit_tree/show.html.haml +++ b/app/views/projects/edit_tree/show.html.haml @@ -23,16 +23,11 @@ %i.fa.fa-spinner.fa-spin = render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}" - .form-actions - = hidden_field_tag 'last_commit', @last_commit - = hidden_field_tag 'content', '', id: "file-content" - = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] - .commit-button-annotation - = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-primary' - .message - to branch - %strong= @ref - = link_to "Cancel", @after_edit_path, class: "btn btn-cancel", data: { confirm: leave_edit_message} + = hidden_field_tag 'last_commit', @last_commit + = hidden_field_tag 'content', '', id: "file-content" + = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] + = render 'projects/commit_button', ref: @ref, + cancel_path: @after_edit_path :javascript ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace") diff --git a/app/views/projects/import.html.haml b/app/views/projects/import.html.haml index 1f7fd26c646..4513c89e784 100644 --- a/app/views/projects/import.html.haml +++ b/app/views/projects/import.html.haml @@ -19,12 +19,13 @@ = form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f| .form-group.import-url-data = f.label :import_url, class: 'control-label' do - %span Import existing repo + %span Import existing git repo .col-sm-10 = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git' .bs-callout.bs-callout-info This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. %br The import will time out after 4 minutes. For big repositories, use a clone/push combination. + For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"} .form-actions = f.submit 'Retry import', class: "btn btn-create", tabindex: 4 diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index e089b5fa1cf..b125706781c 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -26,6 +26,10 @@ %span %i.fa.fa-clock-o = issue.milestone.title + - if issue.tasks? + %span.task-status + = issue.task_status + .pull-right %small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')} diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 4c1ea098d98..71eb0d5c866 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -48,7 +48,7 @@ .description .wiki = preserve do - = markdown @issue.description + = markdown(@issue.description, parse_tasks: true) .context %cite.cgray = render partial: 'issue_context', locals: { issue: @issue } @@ -62,7 +62,8 @@ = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue" .participants - %cite.cgray #{@issue.participants.count} participants + %cite.cgray + = pluralize(@issue.participants.count, 'participant') - @issue.participants.each do |participant| = link_to_member(@project, participant, name: false, size: 24) diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 647e8873e9e..1ee2e1bdae8 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -27,7 +27,9 @@ %span %i.fa.fa-clock-o = merge_request.milestone.title - + - if merge_request.tasks? + %span.task-status + = merge_request.task_status .pull-right %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')} diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 947e8f58ae5..7b28dd5e7da 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -21,7 +21,7 @@ - content_for :note_actions do - if can?(current_user, :modify_merge_request, @merge_request) - - unless @merge_request.closed? || @merge_request.merged? + - if @merge_request.open? = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request" - if @merge_request.closed? = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request" diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml index 213e14268c2..4939ae03994 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -16,15 +16,6 @@ %h4 You can accept this request automatically. .accept-merge-holder.clearfix - .js-toggle-container - %p - You can - %strong= link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" - before accepting merge request - .js-toggle-content.hide - = render 'shared/commit_message_container', params: params, - text: @merge_request.merge_commit_message, - rows: 14, hint: true .accept-group .pull-left = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request" @@ -33,6 +24,14 @@ = label_tag :should_remove_source_branch, class: "checkbox" do = check_box_tag :should_remove_source_branch Remove source-branch + .js-toggle-container + %label + %i.fa.fa-edit + = link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" + .js-toggle-content.hide + = render 'shared/commit_message_container', params: params, + text: @merge_request.merge_commit_message, + rows: 14, hint: true %hr .light diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index f1aaba2010d..7e5a4eda508 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -18,7 +18,7 @@ .description .wiki = preserve do - = markdown @merge_request.description + = markdown(@merge_request.description, parse_tasks: true) .context %cite.cgray diff --git a/app/views/projects/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml index dc64c096edc..941b15d3b32 100644 --- a/app/views/projects/merge_requests/show/_mr_ci.html.haml +++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml @@ -20,9 +20,8 @@ = link_to "Build page", ci_build_details_path(@merge_request) .ci_widget - %strong - %i.fa.fa-spinner - Checking for CI status for #{@merge_request.last_commit_short_sha} + %i.fa.fa-spinner + Checking for CI status for #{@merge_request.last_commit_short_sha} .ci_widget.ci-error{style: "display:none"} %i.fa.fa-times diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml index 5db77ab2754..87dad6140be 100644 --- a/app/views/projects/merge_requests/show/_state_widget.html.haml +++ b/app/views/projects/merge_requests/show/_state_widget.html.haml @@ -1,8 +1,8 @@ -.panel.mr-state-widget.panel-default +.mr-state-widget - if @merge_request.source_project.ci_service && @commits.any? - .panel-heading + .mr-widget-heading = render "projects/merge_requests/show/mr_ci" - .panel-body + .mr-widget-body - if @merge_request.open? - if @merge_request.source_branch_exists? && @merge_request.target_branch_exists? = render "projects/merge_requests/show/mr_accept" @@ -21,6 +21,12 @@ #{time_ago_with_tooltip(@merge_request.merge_event.created_at)} = render "projects/merge_requests/show/remove_source_branch" + - if @merge_request.locked? + %h4 + Merge in progress... + %p + GitLab tries to merge it right now. During this time merge request is locked and can not be closed. + - unless @commits.any? %h4 Nothing to merge %p @@ -31,8 +37,8 @@ %br Try to use different branches or push new code. - - if !@closes_issues.empty? && @merge_request.open? - .panel-footer + - if @closes_issues.present? && @merge_request.open? + .mr-widget-footer %span %i.fa.fa-check Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'} diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 6c986050c45..f5cd0f21e01 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -44,13 +44,14 @@ .js-toggle-content.hide .form-group.import-url-data = f.label :import_url, class: 'control-label' do - %span Import existing repo + %span Import existing git repo .col-sm-10 = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git' .bs-callout.bs-callout-info This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. %br The import will time out after 4 minutes. For big repositories, use a clone/push combination. + For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"} %hr .form-group diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml index 49c504c104f..c47c0a3f642 100644 --- a/app/views/projects/new_tree/show.html.haml +++ b/app/views/projects/new_tree/show.html.haml @@ -27,14 +27,9 @@ .file-content.code %pre#editor= params[:content] - .form-actions - = hidden_field_tag 'content', '', id: "file-content" - .commit-button-annotation - = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-create' - .message - to branch - %strong= @ref - = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message} + = hidden_field_tag 'content', '', id: 'file-content' + = render 'projects/commit_button', ref: @ref, + cancel_path: project_tree_path(@project, @id) :javascript ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict") diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml index da71220af17..b4d1cce7980 100644 --- a/app/views/projects/notes/discussions/_diff.html.haml +++ b/app/views/projects/notes/discussions/_diff.html.haml @@ -21,7 +21,7 @@ - else %td.old_line= raw(line.type == "new" ? " " : line.old_pos) %td.new_line= raw(line.type == "old" ? " " : line.new_pos) - %td.line_content{class: "noteable_line #{line.type} #{line_code}", "line_code" => line_code}= raw "#{line.text} " + %td.line_content{class: "noteable_line #{line.type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text) - if line_code == note.line_code = render "projects/notes/diff_notes_with_reply", notes: discussion_notes diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 49a3ef4c8a7..227a2f9a061 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -1,13 +1,13 @@ %h3.page-title Protected branches -%p.light This ability keeps stable branches secure and forces developers to use code reviews +%p.light Keep stable branches secure and force developers to use Merge Requests %hr .bs-callout.bs-callout-info %p Protected branches are designed to %ul %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} - %li prevents anyone from force pushing to the branch - %li prevents anyone from deleting the branch + %li prevent anyone from force pushing to the branch + %li prevent anyone from deleting the branch %p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"} - if can? current_user, :admin_project, @project diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 09664ed51eb..9b06ebe95a4 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -62,11 +62,14 @@ - else #{link_to @project.owner_name, @project.owner} - - - if @project.gitlab_ci? - %hr - = link_to @project.gitlab_ci_service.builds_path do - = image_tag @project.gitlab_ci_service.status_img_path, alt: "build status" + - @project.ci_services.each do |ci_service| + - if ci_service.active? && ci_service.respond_to?(:builds_path) + - if ci_service.respond_to?(:status_img_path) + = link_to ci_service.builds_path do + = image_tag ci_service.status_img_path, alt: "build status" + - else + %span.light CI provided by + = link_to ci_service.title, ci_service.builds_path - if readme .tab-pane#tab-readme diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml index a8ec9df2c8f..46e9be4af83 100644 --- a/app/views/projects/tree/_submodule_item.html.haml +++ b/app/views/projects/tree/_submodule_item.html.haml @@ -7,8 +7,8 @@ @ %span.monospace - if commit.nil? - #{submodule_item.id[0..10]} + #{truncate_sha(submodule_item.id)} - else - = link_to "#{submodule_item.id[0..10]}", commit + = link_to "#{truncate_sha(submodule_item.id)}", commit %td %td.hidden-xs diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index d3a66c48c9b..ef4b8f74714 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -17,7 +17,7 @@ %tr %td = link_to project_wiki_path(@project, @page, version_id: commit.id) do - = commit.id[0..10] + = truncate_sha(commit.id) %td = commit.author.name %td diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml index f2327cd69cc..a44a4542df5 100644 --- a/app/views/search/results/_note.html.haml +++ b/app/views/search/results/_note.html.haml @@ -10,7 +10,7 @@ = project.name_with_namespace · = link_to project_commit_path(project, note.commit_id, anchor: dom_id(note)) do - Commit #{note.commit_id[0..8]} + Commit #{truncate_sha(note.commit_id)} - else = link_to project do = project.name_with_namespace 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 00000000000..f32c2d388a7 --- /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/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml new file mode 100644 index 00000000000..30ba361c860 --- /dev/null +++ b/app/views/shared/_confirm_modal.html.haml @@ -0,0 +1,22 @@ +#modal-confirm-danger.modal.hide{tabindex: -1} + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h4 Confirmation required + + .modal-body + %p.cred.lead.js-confirm-text + + %p + This action can lead to data loss. + To prevent accidental actions we ask you to confirm your intention. + %br + Please type + %code.js-confirm-danger-match #{phrase} + to proceed or close this modal to cancel + + .form-group + = text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input' + .form-group + = submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit" diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml new file mode 100644 index 00000000000..93294e42505 --- /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 00000000000..e5cf783beb7 --- /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/shared/_promo.html.haml b/app/views/shared/_promo.html.haml index 5675e43b05f..3400c345c4c 100644 --- a/app/views/shared/_promo.html.haml +++ b/app/views/shared/_promo.html.haml @@ -1,5 +1,5 @@ .gitlab-promo - = link_to "Homepage", "https://www.gitlab.com/" - = link_to "Blog", "https://www.gitlab.com/blog/" + = link_to 'Homepage', promo_url + = link_to "Blog", promo_url + '/blog/' = link_to "@gitlabhq", "https://twitter.com/gitlabhq" = link_to "Requests", "http://feedback.gitlab.com/" diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index 49ea8460e7d..f729f129e45 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -10,22 +10,8 @@ = f.label :title, class: 'control-label' .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true - - unless @snippet.respond_to?(:project) - .form-group - = f.label "Access", class: 'control-label' - .col-sm-10 - = f.label :private_true, class: 'radio-label' do - = f.radio_button :private, true - %span - %strong Private - (only you can see this snippet) - %br - = f.label :private_false, class: 'radio-label' do - = f.radio_button :private, false - %span - %strong Public - (GitLab users can see this snippet) - + = render "shared/snippets/visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true + .form-group .file-editor = f.label :file_name, "File", class: 'control-label' diff --git a/app/views/shared/snippets/_visibility_level.html.haml b/app/views/shared/snippets/_visibility_level.html.haml new file mode 100644 index 00000000000..9acff18e450 --- /dev/null +++ b/app/views/shared/snippets/_visibility_level.html.haml @@ -0,0 +1,27 @@ +.form-group.project-visibility-level-holder + = f.label :visibility_level, class: 'control-label' do + Visibility Level + = link_to "(?)", help_page_path("public_access", "public_access") + .col-sm-10 + - if can_change_visibility_level + - Gitlab::VisibilityLevel.values.each do |level| + .radio + - restricted = restricted_visibility_levels.include?(level) + = f.radio_button :visibility_level, level, disabled: restricted + = label "#{dom_class(@snippet)}_visibility_level", level do + = visibility_level_icon(level) + .option-title + = visibility_level_label(level) + .option-descr + = snippet_visibility_level_description(level) + - unless restricted_visibility_levels.empty? + .col-sm-10 + %span.info + Some visibility level settings have been restricted by the administrator. + - else + .col-sm-10 + %span.info + = visibility_level_icon(visibility_level) + %strong + = visibility_level_label(visibility_level) + .light= visibility_level_description(visibility_level) diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml index e3edd856983..b2b7ea4df0e 100644 --- a/app/views/snippets/current_user_index.html.haml +++ b/app/views/snippets/current_user_index.html.haml @@ -23,6 +23,11 @@ Private %span.pull-right = @user.snippets.are_private.count + = nav_tab :scope, 'are_internal' do + = link_to user_snippets_path(@user, scope: 'are_internal') do + Internal + %span.pull-right + = @user.snippets.are_internal.count = nav_tab :scope, 'are_public' do = link_to user_snippets_path(@user, scope: 'are_public') do Public diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index cea2517a8e1..0d71c41e2e7 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -2,10 +2,12 @@ Public snippets .pull-right - = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do - Add new snippet - = link_to user_snippets_path(current_user), class: "btn btn-grouped" do - My snippets + + - if current_user + = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do + Add new snippet + = link_to user_snippets_path(current_user), class: "btn btn-grouped" do + My snippets %p.light Public snippets created by you and other users are listed here diff --git a/app/views/snippets/user_index.html.haml b/app/views/snippets/user_index.html.haml index 1cb53ec6a25..67f3a68aa22 100644 --- a/app/views/snippets/user_index.html.haml +++ b/app/views/snippets/user_index.html.haml @@ -4,8 +4,9 @@ %span \/ Snippets - = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do - Add new snippet + - if current_user + = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do + Add new snippet %hr diff --git a/config/application.rb b/config/application.rb index 99dfafdb786..85c83f74a95 100644 --- a/config/application.rb +++ b/config/application.rb @@ -2,7 +2,7 @@ require File.expand_path('../boot', __FILE__) require 'rails/all' require 'devise' - +I18n.config.enforce_available_locales = false Bundler.require(:default, Rails.env) module Gitlab @@ -25,6 +25,7 @@ module Gitlab # 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. + # NOTE: Please prefer set time zone on config/gitlab.yml configuration file. # config.time_zone = 'Central Time (US & Canada)' # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 857643c006e..2ca6abac576 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -33,6 +33,11 @@ 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 # Email address used in the "From" field in mails sent by GitLab email_from: example@example.com @@ -119,6 +124,7 @@ production: &base # new_issue_url: "http://jira.sample/secure/CreateIssue.jspa" ## Gravatar + ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html gravatar: enabled: true # Use user avatar image from Gravatar.com (default: true) # gravatar urls: possible placeholders: %{hash} %{size} %{email} @@ -134,43 +140,61 @@ production: &base # bundle exec rake gitlab:ldap:check RAILS_ENV=production ldap: enabled: false - 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: '' + 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' + + 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: + # .... ## OmniAuth settings @@ -299,6 +323,20 @@ test: project_url: "http://redmine/projects/:issues_tracker_id" issues_url: "http://redmine/:project_id/:issues_tracker_id/:id" new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new" + ldap: + enabled: false + servers: + main: + label: ldap + host: 127.0.0.1 + port: 3890 + uid: 'uid' + method: 'plain' # "tls" or "ssl" or "plain" + base: 'dc=example,dc=com' + user_filter: '' + group_base: 'ou=groups,dc=example,dc=com' + admin_group: '' + sync_ssh_keys: false staging: <<: *base diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 0d11ae6f33f..4670791ddb0 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -56,9 +56,25 @@ end # Default settings Settings['ldap'] ||= Settingslogic.new({}) Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil? -Settings.ldap['allow_username_or_email_login'] = false if Settings.ldap['allow_username_or_email_login'].nil? -Settings.ldap['active_directory'] = true if Settings.ldap['active_directory'].nil? +# backwards compatibility, we only have one host +if Settings.ldap['enabled'] || Rails.env.test? + if Settings.ldap['host'].present? + server = Settings.ldap.except('sync_time') + server['provider_name'] = 'ldap' + Settings.ldap['servers'] = { + 'ldap' => server + } + end + + Settings.ldap['servers'].each do |key, server| + server['label'] ||= 'LDAP' + server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil? + server['active_directory'] = true if server['active_directory'].nil? + server['provider_name'] ||= "ldap#{key}".downcase + server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name']) + end +end Settings['omniauth'] ||= Settingslogic.new({}) Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil? @@ -87,6 +103,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/7_omniauth.rb b/config/initializers/7_omniauth.rb new file mode 100644 index 00000000000..18759f0cfb0 --- /dev/null +++ b/config/initializers/7_omniauth.rb @@ -0,0 +1,12 @@ +if Gitlab::LDAP::Config.enabled? + module OmniAuth::Strategies + server = Gitlab.config.ldap.servers.values.first + klass = server['provider_class'] + const_set(klass, Class.new(LDAP)) unless klass == 'LDAP' + end + + OmniauthCallbacksController.class_eval do + server = Gitlab.config.ldap.servers.values.first + alias_method server['provider_name'], :ldap + end +end
\ No newline at end of file diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 34f4f386988..c6eb3e51036 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -204,22 +204,24 @@ Devise.setup do |config| # manager.default_strategies(scope: :user).unshift :some_external_strategy # end - if Gitlab.config.ldap.enabled - if Gitlab.config.ldap.allow_username_or_email_login - email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')} - else - email_stripping_proc = ->(name) {name} + if Gitlab::LDAP::Config.enabled? + Gitlab.config.ldap.servers.values.each do |server| + if server['allow_username_or_email_login'] + email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')} + else + email_stripping_proc = ->(name) {name} + end + + config.omniauth server['provider_name'], + host: server['host'], + base: server['base'], + uid: server['uid'], + port: server['port'], + method: server['method'], + bind_dn: server['bind_dn'], + password: server['password'], + name_proc: email_stripping_proc end - - config.omniauth :ldap, - host: Gitlab.config.ldap['host'], - base: Gitlab.config.ldap['base'], - uid: Gitlab.config.ldap['uid'], - port: Gitlab.config.ldap['port'], - method: Gitlab.config.ldap['method'], - bind_dn: Gitlab.config.ldap['bind_dn'], - password: Gitlab.config.ldap['password'], - name_proc: email_stripping_proc end Gitlab.config.omniauth.providers.each do |provider| diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb new file mode 100644 index 00000000000..8d2b771e535 --- /dev/null +++ b/config/initializers/gitlab_shell_secret_token.rb @@ -0,0 +1,19 @@ +# Be sure to restart your server when you modify this file. + +require 'securerandom' + +# Your secret key for verifying the gitlab_shell. + + +secret_file = Rails.root.join('.gitlab_shell_secret') +gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret') + +unless File.exist? secret_file + # Generate a new token of 16 random hexadecimal characters and store it in secret_file. + token = SecureRandom.hex(16) + File.write(secret_file, token) +end + +if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink) + FileUtils.symlink(secret_file, gitlab_shell_symlink) +end
\ No newline at end of file diff --git a/config/initializers/time_zone.rb b/config/initializers/time_zone.rb new file mode 100644 index 00000000000..ee246e67d66 --- /dev/null +++ b/config/initializers/time_zone.rb @@ -0,0 +1 @@ +Time.zone = Gitlab.config.gitlab.time_zone || Time.zone diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb index ff91e8430a4..b3a6f39c7d5 100644 --- a/db/fixtures/development/12_snippets.rb +++ b/db/fixtures/development/12_snippets.rb @@ -1,9 +1,26 @@ Gitlab::Seeder.quiet do - contents = [ - `curl https://gist.githubusercontent.com/randx/4275756/raw/da2f262920c96d1a970d48bf2e99147954b1f4bd/glus1204.sh`, - `curl https://gist.githubusercontent.com/randx/3754594/raw/11026a295e6ef3a151c635707a3e1e8e15fc4725/gitlab_setup.sh`, - `curl https://gist.githubusercontent.com/randx/3065552/raw/29fbd09f4605a5ea22a5a9095e35fd1938dea4d6/gistfile1.sh`, - ] + content =<<eos +class Member < ActiveRecord::Base + include Notifiable + include Gitlab::Access + + belongs_to :user + belongs_to :source, polymorphic: true + + validates :user, presence: true + validates :source, presence: true + validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source" } + validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true + + scope :guests, -> { where(access_level: GUEST) } + scope :reporters, -> { where(access_level: REPORTER) } + scope :developers, -> { where(access_level: DEVELOPER) } + scope :masters, -> { where(access_level: MASTER) } + scope :owners, -> { where(access_level: OWNER) } + + delegate :name, :username, :email, to: :user, prefix: true +end +eos (1..50).each do |i| user = User.all.sample @@ -12,10 +29,11 @@ Gitlab::Seeder.quiet do id: i, author_id: user.id, title: Faker::Lorem.sentence(3), - file_name: Faker::Internet.domain_word + '.sh', - private: [true, false].sample, - content: contents.sample, + file_name: Faker::Internet.domain_word + '.rb', + visibility_level: Gitlab::VisibilityLevel.values.sample, + content: content, }]) + print('.') end end diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb index f84d0903910..e0b13db020d 100644 --- a/db/fixtures/production/001_admin.rb +++ b/db/fixtures/production/001_admin.rb @@ -21,7 +21,7 @@ admin.save! admin.confirm! if admin.valid? -puts %q[ +puts %Q[ Administrator account created: login.........root diff --git a/db/migrate/20141006143943_move_slack_service_to_webhook.rb b/db/migrate/20141006143943_move_slack_service_to_webhook.rb new file mode 100644 index 00000000000..4b62b223cbf --- /dev/null +++ b/db/migrate/20141006143943_move_slack_service_to_webhook.rb @@ -0,0 +1,17 @@ +class MoveSlackServiceToWebhook < ActiveRecord::Migration + def change + SlackService.all.each do |slack_service| + if ["token", "subdomain"].all? { |property| slack_service.properties.key? property } + token = slack_service.properties['token'] + subdomain = slack_service.properties['subdomain'] + webhook = "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{token}" + slack_service.properties['webhook'] = webhook + slack_service.properties.delete('token') + slack_service.properties.delete('subdomain') + # Room is configured on the Slack side + slack_service.properties.delete('room') + slack_service.save! + end + end + end +end diff --git a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb new file mode 100644 index 00000000000..7f125acb5d1 --- /dev/null +++ b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb @@ -0,0 +1,21 @@ +class AddVisibilityLevelToSnippet < ActiveRecord::Migration + def up + add_column :snippets, :visibility_level, :integer, :default => 0, :null => false + + Snippet.where(private: true).update_all(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + Snippet.where(private: false).update_all(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + + add_index :snippets, :visibility_level + + remove_column :snippets, :private + end + + def down + add_column :snippets, :private, :boolean, :default => false, :null => false + + Snippet.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).update_all(private: false) + Snippet.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).update_all(private: true) + + remove_column :snippets, :visibility_level + end +end diff --git a/db/schema.rb b/db/schema.rb index 4e249caa022..8ddebc5132a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20140914173417) do +ActiveRecord::Schema.define(version: 20141007100818) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -293,20 +293,21 @@ ActiveRecord::Schema.define(version: 20140914173417) do create_table "snippets", force: true do |t| t.string "title" t.text "content" - t.integer "author_id", null: false + t.integer "author_id", null: false t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" t.string "file_name" t.datetime "expires_at" - t.boolean "private", default: true, null: false t.string "type" + t.integer "visibility_level", default: 0, null: false end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree + add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree create_table "taggings", force: true do |t| t.integer "tag_id" diff --git a/doc/README.md b/doc/README.md index 2f90cf14a64..a8e21f75714 100644 --- a/doc/README.md +++ b/doc/README.md @@ -20,6 +20,7 @@ - [Update](update/README.md) Update guides to upgrade your installation. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. +- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. ## Contributor documentation diff --git a/doc/api/services.md b/doc/api/services.md new file mode 100644 index 00000000000..ab9f9c00c67 --- /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/customization/libravatar.md b/doc/customization/libravatar.md new file mode 100644 index 00000000000..4dffd3027a9 --- /dev/null +++ b/doc/customization/libravatar.md @@ -0,0 +1,69 @@ +# Use Libravatar service with GitLab + +GitLab by default supports [Gravatar](gravatar.com) avatar service. +Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is +[heavily based on gravatar](http://wiki.libravatar.org/api/). + +This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server. + +# Configuration + +In [gitlab.yml gravatar section](https://gitlab.com/gitlab-org/gitlab-ce/blob/672bd3902d86b78d730cea809fce312ec49d39d7/config/gitlab.yml.example#L122) set +the configuration options as follows: + +## For HTTP + +```yml + gravatar: + enabled: true + # gravatar urls: possible placeholders: %{hash} %{size} %{email} + plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon" +``` + +## For HTTPS + +```yml + gravatar: + enabled: true + # gravatar urls: possible placeholders: %{hash} %{size} %{email} + ssl_url: "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon" +``` + +## Self-hosted + +If you are [running your own libravatar service](http://wiki.libravatar.org/running_your_own/) the url will be different in the configuration +but the important part is to provide the same placeholders so GitLab can parse the url correctly. + +For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is + +`http://libravatar.example.com/avatar/%{hash}?s=%{size}&d=identicon` + + +## Omnibus-gitlab example + +In `/etc/gitlab/gitlab.rb`: + +#### For http + +```ruby +gitlab_rails['gravatar_enabled'] = true +gitlab_rails['gravatar_plain_url'] = "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon" +``` + +#### For https + +```ruby +gitlab_rails['gravatar_enabled'] = true +gitlab_rails['gravatar_ssl_url'] = "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon" +``` + + +Run `sudo gitlab-ctl reconfigure` for changes to take effect. + + +## Default URL for missing images + +[Libravatar supports different sets](http://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service. + +In order to use a different set other than `identicon`, replace `&d=identicon` portion of the url with another supported set. +For example, you can use `retro` set in which case url would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"` diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 4624d9f60b6..c4813d22eaa 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -2,7 +2,7 @@ ## Software delivery -There are two editions of GitLab: [Enterprise Edition](https://www.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://www.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development. +There are two editions of GitLab: [Enterprise Edition](https://about.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://about.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development. EE releases are available not long after CE releases. To obtain the GitLab EE there is a [repository at gitlab.com](https://gitlab.com/subscribers/gitlab-ee). For more information about the release process see the section 'New versions and upgrading' in the readme. diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md index b3e84183a41..ee16aedafe7 100644 --- a/doc/development/ci_setup.md +++ b/doc/development/ci_setup.md @@ -4,28 +4,30 @@ This document describes what services we use for testing GitLab and GitLab CI. We currently use three CI services to test GitLab: -1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/2/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce) +1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/4/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce) 2. GitLab CI at ci.gitlab.org to test the private GitLab B.V. repo at dev.gitlab.org 3. [Semephore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for [GitHub.com repo](https://github.com/gitlabhq/gitlabhq) | Software @ configuration being tested | GitLab CI (ci.gitlab.org) | GitLab CI (GitHost.io) | Semaphore | -|---------------------------------------|---------------------------|------------------------|-----------| -| GitLab CE @ MySQL | ✓ | ✓ | | -| GitLab CE @ PostgreSQL | | | ✓ | -| GitLab EE @ MySQL | ✓ | | | -| GitLab CI @ MySQL | ✓ | | | -| GitLab CI @ PostgreSQL | | | ✓ | -| GitLab CI Runner | ✓ | | ✓ | -| GitLab Shell | ✓ | | ✓ | -| GitLab Shell | ✓ | | ✓ | +|---------------------------------------|---------------------------|---------------------------------------------------------------------------|-----------| +| GitLab CE @ MySQL | ✓ | ✓ [Core team can trigger builds](https://gitlab-ce.githost.io/projects/4) | | +| GitLab CE @ PostgreSQL | | | ✓ [Core team can trigger builds](https://semaphoreapp.com/gitlabhq/gitlabhq/branches/master) | +| GitLab EE @ MySQL | ✓ | | | +| GitLab CI @ MySQL | ✓ | | | +| GitLab CI @ PostgreSQL | | | ✓ | +| GitLab CI Runner | ✓ | | ✓ | +| GitLab Shell | ✓ | | ✓ | +| GitLab Shell | ✓ | | ✓ | + +Core team has access to trigger builds if needed for GitLab CE. We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/build_script_gitlab_ce.md) for testing with GitLab CI. # Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq) -Language: Ruby -Ruby verion: 2.1.2 -database.yml: pg +- Language: Ruby +- Ruby verion: 2.1.2 +- database.yml: pg Build commands diff --git a/doc/install/installation.md b/doc/install/installation.md index af6e182cfa0..7a39f2eec9f 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -1,18 +1,22 @@ # Installation +## Consider the Omnibus package installation + +Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm). + ## Select Version to Install Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases this should be the highest numbered stable branch (example shown below).  -If the highest number stable branch is unclear please check the [GitLab Blog](https://www.gitlab.com/blog/) for installation guide links by version. +If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version. ## Important Notes This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880). -This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://www.gitlab.com/downloads/). +This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/). This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation). @@ -70,8 +74,8 @@ Is the system packaged Git too old? Remove it and compile from source. # Download and compile from source cd /tmp - curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.0.0.tar.gz | tar xz - cd git-2.0.0/ + curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz + cd git-2.1.2/ make prefix=/usr/local all # Install into /usr/local/bin @@ -161,9 +165,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-3-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-4-stable gitlab -**Note:** You can change `7-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `7-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 49edf36f574..3e4c6a28c0e 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -85,9 +85,9 @@ Redis stores all user sessions and the background task queue. The storage requirements for Redis are minimal, about 25kB per user. Sidekiq processes the background jobs with a multithreaded process. This process starts with the entire Rails stack (200MB+) but it can grow over time due to memory leaks. -On a very active server (10.000 active users) the Sidekiq process can use 1GB+ of memory. +On a very active server (10,000 active users) the Sidekiq process can use 1GB+ of memory. -## Supported webbrowsers +## Supported web browsers - Chrome (Latest stable version) - Firefox (Latest released version) diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index ee472ac3e3b..56b0d826adb 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -6,6 +6,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. @@ -24,15 +113,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/markdown/markdown.md b/doc/markdown/markdown.md index 5c095ed1487..edb7a975503 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -10,6 +10,7 @@ * [Code and Syntax Highlighting](#code-and-syntax-highlighting) * [Emoji](#emoji) * [Special GitLab references](#special-gitlab-references) +* [Task lists](#task-lists) **[Standard Markdown](#standard-markdown)** @@ -183,6 +184,18 @@ GFM also recognizes references to commits, issues, and merge requests in other p - namespace/project!123 : for merge requests - namespace/project@1234567 : for commits +## Task Lists + +You can add task lists to merge request and issue descriptions to keep track of to-do items. To create a task, add an unordered list to the description in an issue or merge request, formatted like so: + +```no-highlight +* [x] Completed task +* [ ] Unfinished task + * [x] Nested task +``` + +Task lists can only be created in descriptions, not in titles or comments. Task item state can be managed by editing the description's Markdown or by clicking the rendered checkboxes. + # Standard Markdown ## Headers @@ -497,6 +510,10 @@ Code above produces next output: | cell 1 | cell 2 | | cell 3 | cell 4 | +**Note** + +The row of dashes between the table header and body must have at least three dashes in each column. + ## References - This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). diff --git a/doc/release/monthly.md b/doc/release/monthly.md index c46a3ed9c93..a9253339e5a 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -191,6 +191,7 @@ It is important to do this as soon as possible, so we can catch any errors befor - Ask Dmitriy to add screenshots to the WIP MR. - Decide with team who will be the MVP user. - 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 diff --git a/doc/release/security.md b/doc/release/security.md index da442de6ee1..79d23c02ea4 100644 --- a/doc/release/security.md +++ b/doc/release/security.md @@ -8,7 +8,7 @@ Do a security release when there is a critical issue that needs to be addresses ## Security vulnerability disclosure -Please report suspected security vulnerabilities in private to <support@gitlab.com>, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. +Please report suspected security vulnerabilities in private to <support@gitlab.com>, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. ## Release Procedure @@ -21,7 +21,7 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c 1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the security fix is for EE only) 1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq) 1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number -1. Add the security researcher to the [Security Researcher Acknowledgments list](http://www.gitlab.com/vulnerability-acknowledgements/) +1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/) 1. Thank the security researcher in an email for their cooperation 1. Update the blog post and the CHANGELOG when we receive the CVE number diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md index 897cd0b91fa..cde679598f7 100644 --- a/doc/update/4.2-to-5.0.md +++ b/doc/update/4.2-to-5.0.md @@ -195,6 +195,12 @@ sudo rm -R tmp sudo -u git -H mkdir tmp sudo chmod -R u+rwX tmp/ +# create directory for pids, make sure GitLab can write to it +sudo -u git -H mkdir tmp/pids/ +sudo chmod -R u+rwX tmp/pids/ + +# if you are already running a newer version of GitLab check that installation guide for other tmp folders you need to create + # reboot system sudo reboot diff --git a/doc/update/6.x-or-7.x-to-7.3.md b/doc/update/6.x-or-7.x-to-7.4.md index 171fcb4033a..e923060223b 100644 --- a/doc/update/6.x-or-7.x-to-7.3.md +++ b/doc/update/6.x-or-7.x-to-7.4.md @@ -1,6 +1,6 @@ -# From 6.x or 7.x to 7.3 +# From 6.x or 7.x to 7.4 -This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.3. +This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.4. ## Global issue numbers @@ -64,13 +64,13 @@ sudo gem install bundler --no-ri --no-rdoc ```bash cd /home/git/gitlab sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically ``` For GitLab Community Edition: ```bash -sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically -sudo -u git -H git checkout 7-3-stable +sudo -u git -H git checkout 7-4-stable ``` OR @@ -78,8 +78,7 @@ OR For GitLab Enterprise Edition: ```bash -sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically -sudo -u git -H git checkout 7-3-stable-ee +sudo -u git -H git checkout 7-4-stable-ee ``` ## 4. Install additional packages @@ -153,14 +152,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab TIP: to see what changed in `gitlab.yml.example` in this release use next command: ``` -git diff 6-0-stable:config/gitlab.yml.example 7-3-stable:config/gitlab.yml.example +git diff 6-0-stable:config/gitlab.yml.example 7-4-stable:config/gitlab.yml.example ``` -* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/unicorn.rb.example but with your settings. -* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.0/config.yml.example but with your settings. -* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings. -* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/unicorn.rb.example but with your settings. +* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.1/config.yml.example but with your settings. +* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your settings. * Copy rack attack middleware config ```bash diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md index 329b763322a..ebdd4ff60fa 100644 --- a/doc/update/7.2-to-7.3.md +++ b/doc/update/7.2-to-7.3.md @@ -18,12 +18,12 @@ sudo service gitlab stop ```bash cd /home/git/gitlab sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically ``` For GitLab Community Edition: ```bash -sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically sudo -u git -H git checkout 7-3-stable ``` @@ -32,7 +32,6 @@ OR For GitLab Enterprise Edition: ```bash -sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically sudo -u git -H git checkout 7-3-stable-ee ``` @@ -75,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 new file mode 100644 index 00000000000..193f44bb67e --- /dev/null +++ b/doc/update/7.3-to-7.4.md @@ -0,0 +1,217 @@ +# From 7.3 to 7.4 + +### 0. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + +```bash +sudo service gitlab stop +``` + +### 2. Get latest code + +```bash +cd /home/git/gitlab +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 7-4-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-4-stable-ee +``` + +### 3. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without ... postgres') +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + + +### 4. Configure Redis to use sockets + + # Configure redis to use sockets + sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig + # Disable Redis listening on TCP by setting 'port' to 0 + sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf + # 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 + # Activate the changes to redis.conf + sudo service redis-server restart + # Add git to the redis group + sudo usermod -aG redis git + + # Configure Redis connection settings + sudo -u git -H cp config/resque.yml.example config/resque.yml + # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration + sudo -u git -H editor config/resque.yml + + # Configure gitlab-shell to use Redis sockets + sudo -u git -H sed -i 's|^ # socket.*| socket: /var/run/redis/redis.sock|' /home/git/gitlab-shell/config.yml + +### 5. Update config files + +#### New configuration options for gitlab.yml + +There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml. + +``` +git diff origin/7-3-stable:config/gitlab.yml.example origin/7-4-stable:config/gitlab.yml.example +``` + +#### Change timeout for unicorn + +``` +# config/unicorn.rb +timeout 60 +``` + +#### Change nginx https settings + +* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting + +#### Update database.yml config file(for mysql only) if needed (basically it is required for old gitlab installations) + +* Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql) + + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade is complete! + +### 8. Update OmniAuth configuration + +When using Google omniauth login, changes of the Google account required. +Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/). +More details can be found at the [integration documentation](../integration/google.md). + +### 9. Optional optimizations for GitLab setups with MySQL databases + +Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand. + +``` +# Secure your MySQL installation (added in GitLab 6.2) +sudo mysql_secure_installation + +# Login to MySQL +mysql -u root -p + +# do not type the 'mysql>', this is part of the prompt + +# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8) +SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE'; + +# If previous query returned results, copy & run all outputed SQL statements + +# Convert all tables to correct character set +SET foreign_key_checks = 0; +SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE'; + +# If previous query returned results, copy & run all outputed SQL statements + +# turn foreign key checks back on +SET foreign_key_checks = 1; + +# Find MySQL users +mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%'; + +# If git user exists and gitlab user does not exist +# you are done with the database cleanup tasks +mysql> \q + +# If both users exist skip to Delete gitlab user + +# Create new user for GitLab (changed in GitLab 6.4) +# change $password in the command below to a real password you pick +mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password'; + +# Grant the git user necessary permissions on the database +mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost'; + +# Delete the old gitlab user +mysql> DELETE FROM mysql.user WHERE user='gitlab'; + +# Quit the database session +mysql> \q + +# Try connecting to the new database with the new user +sudo -u git -H mysql -u git -p -D gitlabhq_production + +# Type the password you replaced $password with earlier + +# You should now see a 'mysql>' prompt + +# Quit the database session +mysql> \q + +# Update database configuration details +# See config/database.yml.mysql for latest recommended configuration details +# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8) +# Set production -> pool: 10 (updated in GitLab 5.3) +# 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 (7.3) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.2 to 7.3](7.2-to-7.3.md), except for the database migration +(The backup is already migrated to the previous version) + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` +If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above. + + + + diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 323ee48f3bc..c26d85e9955 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -4,3 +4,5 @@ - [Groups](groups.md) - [Labels](labels.md) - [GitLab Flow](gitlab_flow.md) +- [Notifications](notifications.md) +- [Migrating from SVN to GitLab](migrating_from_svn.md) diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md index 947646c756a..f8fd7c97e2a 100644 --- a/doc/workflow/gitlab_flow.md +++ b/doc/workflow/gitlab_flow.md @@ -26,7 +26,7 @@ After getting used to these three steps the branching model becomes the challeng Since many organizations new to git have no conventions how to work with it, it can quickly become a mess. The biggest problem they run into is that many long running branches that each contain part of the changes are around. People have a hard time figuring out which branch they should develop on or deploy to production. -Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](https://guides.github.com/introduction/flow/index.html) +Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html) We think there is still room for improvement and will detail a set of practices we call GitLab flow. # Git flow and its problems @@ -309,3 +309,8 @@ If you need to merge in another branch after starting explain the reason in the If you have not pushed your commits to a shared location yet you can also rebase on master or another feature branch. Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](http://lwn.net/Articles/328438/). Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history. + +### References + +- [Sketch file](https://www.dropbox.com/s/58dvsj5votbwrzv/git_flows.sketch?dl=0) with vectors of images in this article +- [Git Flow by Vincent Driessen](http://nvie.com/posts/a-successful-git-branching-model/) diff --git a/doc/workflow/migrating_from_svn.md b/doc/workflow/migrating_from_svn.md new file mode 100644 index 00000000000..207e3641802 --- /dev/null +++ b/doc/workflow/migrating_from_svn.md @@ -0,0 +1,17 @@ +# Migrating from SVN to GitLab + +SVN stands for Subversion and is a version control system (VCS). +Git is a distributed version control system. + +There are some major differences between the two, for more information consult your favourite search engine. + +Git has tools for migrating SVN repositories to git, namely `git svn`. You can read more about this at +[git documentation pages](http://git-scm.com/book/en/Git-and-Other-Systems-Git-and-Subversion). + +Apart from the [official git documentation](http://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git) there is also +user created step by step guide for migrating from SVN to GitLab. + +[Benjamin New](https://github.com/leftclickben) wrote [a guide that shows how to do a migration](https://gist.github.com/leftclickben/322b7a3042cbe97ed2af). Mirrors can be found [here](https://gitlab.com/snippets/2168) and [here](https://gist.github.com/maxlazio/f1b593b0d00aa966e9ca). + +## Contribute to this guide +We welcome all contributions that would expand this guide with instructions on how to migrate from SVN and other version control systems. diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md new file mode 100644 index 00000000000..3c3ce162df5 --- /dev/null +++ b/doc/workflow/notifications.md @@ -0,0 +1,71 @@ +# GitLab Notifications + +GitLab has notifications system in place to notify a user of events important for the workflow. + +## Notification settings + +Under user profile page you can find the notification settings. + + + +Notification settings are divided into three groups: + +* Global Settings +* Group Settings +* Project Settings + +Each of these settings have levels of notification: + +* Disabled - turns off notifications +* Participating - receive notifications from related resources +* Watch - receive notifications from projects or groups user is a member of +* Global - notifications as set at the global settings + +#### Global Settings + +Global Settings are at the bottom of the hierarchy. +Any setting set here will be overriden by a setting at the group or a project level. + +Group or Project settings can use `global` notification setting which will then use +anything that is set at Global Settings. + +#### Group Settings + +Group Settings are taking presedence over Global Settings but are on a level below Project Settings. +This means that you can set a different level of notifications per group while still being able +to have a finer level setting per project. +Organization like this is suitable for users that belong to different groups but don't have the +same need for being notified for every group they are member of. + +#### Project Settings + +Project Settings are at the top level and any setting placed at this level will take presedence of any +other setting. +This is suitable for users that have different needs for notifications per project basis. + +## Notification events + +Below is the table of events users can be notified of: + +| Event | Sent to | Settings level | +|------------------------------|-------------------------------------------------------------------|------------------------------| +| New SSH key added | User | Security email, always sent. | +| New email added | User | Security email, always sent. | +| New user created | User | Sent on user creation, except for omniauth (LDAP)| +| New issue created | Issue assignee [1], project members [2] | [1] not disabled, [2] higher than participating | +| User added to project | User | Sent when user is added to project | +| Project access level changed | User | Sent when user project access level is changed | +| User added to group | User | Sent when user is added to group | +| Project moved | Project members [1] | [1] not disabled | +| Group access level changed | User | Sent when user group access level is changed | +| Close issue | Issue author [1], issue assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating | +| Reassign issue | New issue assignee [1], old issue assignee [2] | [1] [2] not disabled | +| Reopen issue | Project members [1] | [1] higher than participating | +| New merge request | MR assignee [1] | [1] not disabled | +| Reassign merge request | New MR assignee [1], old MR assignee [2] | [1] [2] not disabled | +| Close merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating | +| Reopen merge request | Project members [1] | [1] higher than participating | +| Merge merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating | +| New comment | Mentioned users [1], users participating [2], project members [3] | [1] [2] not disabled, [3] higher than participating | + + diff --git a/doc/workflow/notifications/settings.png b/doc/workflow/notifications/settings.png Binary files differnew file mode 100644 index 00000000000..e5b50ee2494 --- /dev/null +++ b/doc/workflow/notifications/settings.png diff --git a/features/admin/active_tab.feature b/features/admin/active_tab.feature index b28e16f0d6a..5de07e90e28 100644 --- a/features/admin/active_tab.feature +++ b/features/admin/active_tab.feature @@ -1,5 +1,5 @@ @admin -Feature: Admin active tab +Feature: Admin Active Tab Background: Given I sign in as an admin diff --git a/features/admin/groups.feature b/features/admin/groups.feature index 1a465c1be55..aa365a6ea1a 100644 --- a/features/admin/groups.feature +++ b/features/admin/groups.feature @@ -20,3 +20,10 @@ Feature: Admin Groups When I visit admin group page When I select user "John Doe" from user list as "Reporter" Then I should see "John Doe" in team list in every project as "Reporter" + + @javascript + Scenario: Remove user from group + Given we have user "John Doe" in group + When I visit admin group page + And I remove user "John Doe" from group + Then I should not see "John Doe" in team list diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index ae6a03ce865..4db8551559b 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -126,3 +126,36 @@ Feature: Project Issues When I click label 'bug' And I should see "Release 0.4" in issues And I should not see "Tweet control" in issues + + Scenario: Issue description should render task checkboxes + Given project "Shop" has "Tasks-open" open issue with task markdown + When I visit issue page "Tasks-open" + Then I should see task checkboxes in the description + + @javascript + Scenario: Issue notes should not render task checkboxes + Given project "Shop" has "Tasks-open" open issue with task markdown + When I visit issue page "Tasks-open" + And I leave a comment with task markdown + Then I should not see task checkboxes in the comment + + # Task status in issues list + + Scenario: Issues list should display task status + Given project "Shop" has "Tasks-open" open issue with task markdown + When I visit project "Shop" issues page + Then I should see the task status for the Taskable + + # Toggling task items + + @javascript + Scenario: Task checkboxes should be enabled for an open issue + Given project "Shop" has "Tasks-open" open issue with task markdown + When I visit issue page "Tasks-open" + Then Task checkboxes should be enabled + + @javascript + Scenario: Task checkboxes should be disabled for a closed issue + Given project "Shop" has "Tasks-closed" closed issue with task markdown + When I visit issue page "Tasks-closed" + Then Task checkboxes should be disabled diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index f8dccc15c0e..d20358a7dc6 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -96,6 +96,16 @@ Feature: Project Merge Requests And I leave a comment with a header containing "Comment with a header" Then The comment with the header should not have an ID + Scenario: Merge request description should render task checkboxes + Given project "Shop" has "MR-task-open" open MR with task markdown + When I visit merge request page "MR-task-open" + Then I should see task checkboxes in the description + + Scenario: Merge request notes should not render task checkboxes + Given project "Shop" has "MR-task-open" open MR with task markdown + When I visit merge request page "MR-task-open" + Then I should not see task checkboxes in the comment + # Toggling inline comments @javascript @@ -105,7 +115,7 @@ Feature: Project Merge Requests And I switch to the diff tab And I leave a comment like "Line is wrong" on line 39 of the second file And I click link "Hide inline discussion" of the second file - Then I should not see a comment like "Line is wrong" in the second file + Then I should not see a comment like "Line is wrong here" in the second file @javascript Scenario: I show comments on a merge request diff with comments in a single file @@ -113,8 +123,6 @@ Feature: Project Merge Requests And I visit merge request page "Bug NS-05" And I switch to the diff tab And I leave a comment like "Line is wrong" on line 39 of the second file - And I click link "Hide inline discussion" of the second file - And I click link "Show inline discussion" of the second file Then I should see a comment like "Line is wrong" in the second file @javascript @@ -125,7 +133,7 @@ Feature: Project Merge Requests And I leave a comment like "Line is correct" on line 12 of the first file And I leave a comment like "Line is wrong" on line 39 of the second file And I click link "Hide inline discussion" of the second file - Then I should not see a comment like "Line is wrong" in the second file + Then I should not see a comment like "Line is wrong here" in the second file And I should still see a comment like "Line is correct" in the first file @javascript @@ -157,3 +165,25 @@ Feature: Project Merge Requests And I leave a comment like "Line is wrong" on line 39 of the second file And I click Side-by-side Diff tab Then I should see comments on the side-by-side diff page + + # Task status in issues list + + Scenario: Merge requests list should display task status + Given project "Shop" has "MR-task-open" open MR with task markdown + When I visit project "Shop" merge requests page + Then I should see the task status for the Taskable + + # Toggling task items + + @javascript + Scenario: Task checkboxes should be enabled for an open merge request + Given project "Shop" has "MR-task-open" open MR with task markdown + When I visit merge request page "MR-task-open" + Then Task checkboxes should be enabled + + @javascript + Scenario: Task checkboxes should be disabled for a closed merge request + Given project "Shop" has "MR-task-open" open MR with task markdown + And I visit merge request page "MR-task-open" + And I click link "Close" + Then Task checkboxes should be disabled diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index 20ef7ac5702..b7d70881d56 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -1,4 +1,4 @@ -Feature: Project Source Browse files +Feature: Project Source Browse Files Background: Given I sign in as a user And I own project "Shop" @@ -30,11 +30,21 @@ Feature: Project Source Browse files And I edit code And I fill the new file name And I fill the commit message - And I click on "Commit changes" + And I click on "Commit Changes" Then I am redirected to the new file And I should see its new content @javascript + Scenario: If I enter an illegal file name I see an error message + Given I click on "new file" link in repo + And I fill the new file name with an illegal name + And I edit code + And I fill the commit message + And I click on "Commit changes" + Then I am on the new file page + And I see a commit error message + + @javascript Scenario: I can edit file Given I click on ".gitignore" file in repo And I click button "Edit" @@ -46,10 +56,20 @@ Feature: Project Source Browse files And I click button "Edit" And I edit code And I fill the commit message - And I click on "Commit changes" + And I click on "Commit Changes" Then I am redirected to the ".gitignore" And I should see its new content + @javascript @wip + Scenario: If I don't change the content of the file I see an error message + Given I click on ".gitignore" file in repo + And I click button "edit" + And I fill the commit message + And I click on "Commit changes" + # Test fails because carriage returns are added to the file. + Then I am on the ".gitignore" edit file page + And I see a commit error message + @javascript Scenario: I can see editing preview Given I click on ".gitignore" file in repo diff --git a/features/snippets/discover.feature b/features/snippets/discover.feature index 5094062c8c3..1a7e132ea25 100644 --- a/features/snippets/discover.feature +++ b/features/snippets/discover.feature @@ -4,8 +4,10 @@ Feature: Snippets Discover Given I sign in as a user And I have public "Personal snippet one" snippet And I have private "Personal snippet private" snippet + And I have internal "Personal snippet internal" snippet Scenario: I should see snippets Given I visit snippets page Then I should see "Personal snippet one" in snippets + And I should see "Personal snippet internal" in snippets And I should not see "Personal snippet private" in snippets diff --git a/features/snippets/user.feature b/features/snippets/user.feature index 424794f73fd..5b5dadb7b39 100644 --- a/features/snippets/user.feature +++ b/features/snippets/user.feature @@ -4,16 +4,19 @@ Feature: Snippets User Given I sign in as a user And I have public "Personal snippet one" snippet And I have private "Personal snippet private" snippet + And I have internal "Personal snippet internal" snippet Scenario: I should see all my snippets Given I visit my snippets page Then I should see "Personal snippet one" in snippets And I should see "Personal snippet private" in snippets + And I should see "Personal snippet internal" in snippets Scenario: I can see only my private snippets Given I visit my snippets page And I click "Private" filter Then I should not see "Personal snippet one" in snippets + And I should not see "Personal snippet internal" in snippets And I should see "Personal snippet private" in snippets Scenario: I can see only my public snippets @@ -21,3 +24,11 @@ Feature: Snippets User And I click "Public" filter Then I should see "Personal snippet one" in snippets And I should not see "Personal snippet private" in snippets + And I should not see "Personal snippet internal" in snippets + + Scenario: I can see only my internal snippets + Given I visit my snippets page + And I click "Internal" filter + Then I should see "Personal snippet internal" in snippets + And I should not see "Personal snippet private" in snippets + And I should not see "Personal snippet one" in snippets diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index 4f0ba05606d..d69a87cd07e 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -37,8 +37,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps end When 'I select user "John Doe" from user list as "Reporter"' do - user = User.find_by(name: "John Doe") - select2(user.id, from: "#user_ids", multiple: true) + select2(user_john.id, from: "#user_ids", multiple: true) within "#new_team_member" do select "Reporter", from: "access_level" end @@ -58,9 +57,29 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps end end + step 'we have user "John Doe" in group' do + current_group.add_user(user_john, Gitlab::Access::REPORTER) + end + + step 'I remove user "John Doe" from group' do + within "#user_#{user_john.id}" do + click_link 'Remove user from group' + end + end + + step 'I should not see "John Doe" in team list' do + within ".group-users-list" do + page.should_not have_content "John Doe" + end + end + protected def current_group @group ||= Group.first end + + def user_john + @user_john ||= User.find_by(name: "John Doe") + end end diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index c054e0e8282..935f313e298 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -8,7 +8,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps commit = @project.repository.commit page.should have_content(@project.name) page.should have_content(commit.message[0..20]) - page.should have_content(commit.id.to_s[0..5]) + page.should have_content(commit.short_id) end step 'I click atom feed link' do diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index b55b3c6c8a2..640603562dd 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -153,6 +153,14 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps author: project.users.first) end + step 'project "Shop" has "Tasks-open" open issue with task markdown' do + create_taskable(:issue, 'Tasks-open') + end + + step 'project "Shop" has "Tasks-closed" closed issue with task markdown' do + create_taskable(:closed_issue, 'Tasks-closed') + end + step 'empty project "Empty Project"' do create :empty_project, name: 'Empty Project', namespace: @user.namespace end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index c101c696253..fae0cec53a6 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -97,6 +97,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps author: project.users.first) end + step 'project "Shop" has "MR-task-open" open MR with task markdown' do + create_taskable(:merge_request, 'MR-task-open') + end + step 'I switch to the diff tab' do visit diffs_project_merge_request_path(project, merge_request) end @@ -107,7 +111,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I click on the commit in the merge request' do within '.mr-commits' do - click_link sample_commit.id[0..8] + click_link Commit.truncate_sha(sample_commit.id) end end @@ -211,6 +215,18 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end end + step 'I should not see a comment like "Line is wrong here" in the second file' do + within '.files [id^=diff]:nth-child(2)' do + page.should_not have_visible_content "Line is wrong here" + end + end + + step 'I should see a comment like "Line is wrong here" in the second file' do + within '.files [id^=diff]:nth-child(2) .note-text' do + page.should have_visible_content "Line is wrong here" + end + end + step 'I leave a comment like "Line is correct" on line 12 of the first file' do init_diff_note_first_file @@ -228,13 +244,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps init_diff_note_second_file within(".js-discussion-note-form") do - fill_in "note_note", with: "Line is wrong" + fill_in "note_note", with: "Line is wrong on here" click_button "Add Comment" end - - within ".files [id^=diff]:nth-child(2) .note-text" do - page.should have_content "Line is wrong" - end end step 'I should still see a comment like "Line is correct" in the first file' do diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index d816fcafbaa..5bd60f99c84 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -108,16 +108,12 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps step 'I fill Slack settings' do check 'Active' - fill_in 'Subdomain', with: 'gitlab' - fill_in 'Room', with: '#gitlab' - fill_in 'Token', with: 'verySecret' + fill_in 'Webhook', with: 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' click_button 'Save' end step 'I should see Slack service settings saved' do - find_field('Subdomain').value.should == 'gitlab' - find_field('Room').value.should == '#gitlab' - find_field('Token').value.should == 'verySecret' + find_field('Webhook').value.should == 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' end step 'I click Pushover service link' do diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 0642302e797..665f5d6d195 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -61,6 +61,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps fill_in :file_name, with: new_file_name end + step 'I fill the new file name with an illegal name' do + fill_in :file_name, with: '.git' + end + step 'I fill the commit message' do fill_in :commit_message, with: 'Not yet a commit message.' end @@ -69,8 +73,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps click_link 'Diff' end - step 'I click on "Commit changes"' do - click_button 'Commit changes' + step 'I click on "Commit Changes"' do + click_button 'Commit Changes' end step 'I click on "Remove"' do @@ -151,6 +155,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps expect(page).not_to have_link('permalink') end + step 'I see a commit error message' do + expect(page).to have_content('Your changes could not be committed') + end + private def set_new_content diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb index 092f2fceb57..8bf138065b0 100644 --- a/features/steps/shared/markdown.rb +++ b/features/steps/shared/markdown.rb @@ -6,7 +6,52 @@ module SharedMarkdown find(:css, "#{parent} h#{level}##{id} > :last-child")[:href].should =~ /##{id}$/ end + def create_taskable(type, title) + desc_text = <<EOT.gsub(/^ {6}/, '') + * [ ] Task 1 + * [x] Task 2 +EOT + + case type + when :issue, :closed_issue + options = { project: project } + when :merge_request + options = { source_project: project, target_project: project } + end + + create( + type, + options.merge(title: title, + author: project.users.first, + description: desc_text) + ) + end + step 'Header "Description header" should have correct id and link' do header_should_have_correct_id_and_link(1, 'Description header', 'description-header') end + + step 'I should see task checkboxes in the description' do + expect(page).to have_selector( + 'div.description li.task-list-item input[type="checkbox"]' + ) + end + + step 'I should see the task status for the Taskable' do + expect(find(:css, 'span.task-status').text).to eq( + '2 tasks (1 done, 1 unfinished)' + ) + end + + step 'Task checkboxes should be enabled' do + expect(page).to have_selector( + 'div.description li.task-list-item input[type="checkbox"]:enabled' + ) + end + + step 'Task checkboxes should be disabled' do + expect(page).to have_selector( + 'div.description li.task-list-item input[type="checkbox"]:disabled' + ) + end end diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb index 4019fe3697a..2b2cb47a715 100644 --- a/features/steps/shared/note.rb +++ b/features/steps/shared/note.rb @@ -119,4 +119,18 @@ module SharedNote page.should_not have_css("#comment-with-a-header") end end + + step 'I leave a comment with task markdown' do + within('.js-main-target-form') do + fill_in 'note[note]', with: '* [x] Task item' + click_button 'Add Comment' + sleep 0.05 + end + end + + step 'I should not see task checkboxes in the comment' do + expect(page).not_to have_selector( + 'li.note div.timeline-content input[type="checkbox"]' + ) + end end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 141ff13b67d..5f292255ce1 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -265,6 +265,15 @@ module SharedPaths visit project_blob_path(@project, File.join(root_ref, '.gitignore')) end + step 'I am on the new file page' do + current_path.should eq(project_new_tree_path(@project, root_ref)) + end + + step 'I am on the ".gitignore" edit file page' do + current_path.should eq(project_edit_tree_path( + @project, File.join(root_ref, '.gitignore'))) + end + step 'I visit project source page for "6d39438"' do visit project_tree_path(@project, "6d39438") end @@ -292,6 +301,16 @@ module SharedPaths visit project_issue_path(issue.project, issue) end + step 'I visit issue page "Tasks-open"' do + issue = Issue.find_by(title: 'Tasks-open') + visit project_issue_path(issue.project, issue) + end + + step 'I visit issue page "Tasks-closed"' do + issue = Issue.find_by(title: 'Tasks-closed') + visit project_issue_path(issue.project, issue) + end + step 'I visit project "Shop" labels page' do project = Project.find_by(name: 'Shop') visit project_labels_path(project) @@ -322,6 +341,16 @@ module SharedPaths visit project_merge_request_path(mr.target_project, mr) end + step 'I visit merge request page "MR-task-open"' do + mr = MergeRequest.find_by(title: 'MR-task-open') + visit project_merge_request_path(mr.target_project, mr) + end + + step 'I visit merge request page "MR-task-closed"' do + mr = MergeRequest.find_by(title: 'MR-task-closed') + visit project_merge_request_path(mr.target_project, mr) + end + step 'I visit project "Shop" merge requests page' do visit project_merge_requests_path(project) end diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb index 5a27e8750cf..432f32defce 100644 --- a/features/steps/shared/snippet.rb +++ b/features/steps/shared/snippet.rb @@ -6,7 +6,7 @@ module SharedSnippet title: "Personal snippet one", content: "Test content", file_name: "snippet.rb", - private: false, + visibility_level: Snippet::PUBLIC, author: current_user) end @@ -15,9 +15,19 @@ module SharedSnippet title: "Personal snippet private", content: "Provate content", file_name: "private_snippet.rb", - private: true, + visibility_level: Snippet::PRIVATE, author: current_user) end + + step 'I have internal "Personal snippet internal" snippet' do + create(:personal_snippet, + title: "Personal snippet internal", + content: "Provate content", + file_name: "internal_snippet.rb", + visibility_level: Snippet::INTERNAL, + author: current_user) + end + step 'I have a public many lined snippet' do create(:personal_snippet, title: 'Many lined snippet', @@ -38,7 +48,7 @@ module SharedSnippet |line fourteen END file_name: 'many_lined_snippet.rb', - private: true, + visibility_level: Snippet::PUBLIC, author: current_user) end end diff --git a/features/steps/snippets/discover.rb b/features/steps/snippets/discover.rb index 42bccafcc84..2667c1e3d44 100644 --- a/features/steps/snippets/discover.rb +++ b/features/steps/snippets/discover.rb @@ -7,6 +7,10 @@ class Spinach::Features::SnippetsDiscover < Spinach::FeatureSteps page.should have_content "Personal snippet one" end + step 'I should see "Personal snippet internal" in snippets' do + page.should have_content "Personal snippet internal" + end + step 'I should not see "Personal snippet private" in snippets' do page.should_not have_content "Personal snippet private" end diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb index dedbdd2c4f0..de936db85ee 100644 --- a/features/steps/snippets/snippets.rb +++ b/features/steps/snippets/snippets.rb @@ -46,7 +46,7 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps end step 'I uncheck "Private" checkbox' do - choose "Public" + choose "Internal" click_button "Save" end diff --git a/features/steps/snippets/user.rb b/features/steps/snippets/user.rb index ca9aa64bee6..866f637ab6c 100644 --- a/features/steps/snippets/user.rb +++ b/features/steps/snippets/user.rb @@ -15,6 +15,10 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps page.should have_content "Personal snippet private" end + step 'I should see "Personal snippet internal" in snippets' do + page.should have_content "Personal snippet internal" + end + step 'I should not see "Personal snippet one" in snippets' do page.should_not have_content "Personal snippet one" end @@ -23,9 +27,13 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps page.should_not have_content "Personal snippet private" end - step 'I click "Public" filter' do + step 'I should not see "Personal snippet internal" in snippets' do + page.should_not have_content "Personal snippet internal" + end + + step 'I click "Internal" filter' do within('.nav-stacked') do - click_link "Public" + click_link "Internal" end end @@ -35,6 +43,12 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps end end + step 'I click "Public" filter' do + within('.nav-stacked') do + click_link "Public" + end + end + def snippet @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") end diff --git a/lib/api/files.rb b/lib/api/files.rb index e63e635a4d3..84e1d311781 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -85,7 +85,7 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + render_api_error!(result[:message], 400) end end @@ -117,7 +117,7 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + render_api_error!(result[:message], 400) end end @@ -149,7 +149,7 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + render_api_error!(result[:message], 400) end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 3262884f6d3..027fb20ec46 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -67,6 +67,10 @@ module API unauthorized! unless current_user end + def authenticate_by_gitlab_shell_token! + unauthorized! unless secret_token == params['secret_token'] + end + def authenticated_as_admin! forbidden! unless current_user.is_admin? end @@ -193,5 +197,9 @@ module API abilities end end + + def secret_token + File.read(Rails.root.join('.gitlab_shell_secret')) + end end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 5f484f63418..ebf2296097d 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -1,6 +1,10 @@ module API # Internal access API class Internal < Grape::API + before { + authenticate_by_gitlab_shell_token! + } + namespace 'internal' do # Check if git command is allowed to project # @@ -14,13 +18,20 @@ module API # post "/allowed" do status 200 + project_path = params[:project] # Check for *.wiki repositories. # Strip out the .wiki from the pathname before finding the # project. This applies the correct project permissions to # the wiki repository as well. - project_path = params[:project] - project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/ + access = + if project_path =~ /\.wiki\Z/ + project_path.sub!(/\.wiki\Z/, '') + Gitlab::GitAccessWiki.new + else + Gitlab::GitAccess.new + end + project = Project.find_with_namespace(project_path) return false unless project @@ -32,7 +43,7 @@ module API return false unless actor - Gitlab::GitAccess.new.allowed?( + access.allowed?( actor, params[:action], project, diff --git a/lib/api/services.rb b/lib/api/services.rb index bde502e32e1..3ad59cf3adf 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/repository.rb b/lib/backup/repository.rb index 4e99d4bbe5c..380beac708d 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -30,7 +30,7 @@ module Backup if File.exists?(path_to_repo(wiki)) print " * #{wiki.path_with_namespace} ... " - if wiki.empty? + if wiki.repository.empty? puts " [SKIPPED]".cyan else output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)) diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb index 8e4717b46e6..dddcb2538f9 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 955abc1bedd..ae33c529b93 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -3,22 +3,16 @@ module Gitlab def find(login, password) user = User.find_by(email: login) || User.find_by(username: login) + # If no user is found, or it's an LDAP server, try LDAP. + # LDAP users are only authenticated via LDAP if user.nil? || user.ldap_user? # Second chance - try LDAP authentication - return nil unless ldap_conf.enabled + return nil unless Gitlab::LDAP::Config.enabled? - Gitlab::LDAP::User.authenticate(login, password) + Gitlab::LDAP::Authentication.login(login, password) else user if user.valid_password?(password) end end - - def log - Gitlab::AppLogger - end - - def ldap_conf - @ldap_conf ||= Gitlab.config.ldap - end end end diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index c2f3b851c07..df1461a45c9 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/git_access.rb b/lib/gitlab/git_access.rb index 6247dd59867..b768a99a0e8 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -49,25 +49,7 @@ module Gitlab # Iterate over all changes to find if user allowed all of them to be applied changes.each do |change| - oldrev, newrev, ref = change.split(' ') - - action = if project.protected_branch?(branch_name(ref)) - # we dont allow force push to protected branch - if forced_push?(project, oldrev, newrev) - :force_push_code_to_protected_branches - # and we dont allow remove of protected branch - elsif newrev =~ /0000000/ - :remove_protected_branches - else - :push_code_to_protected_branches - end - elsif project.repository && project.repository.tag_names.include?(tag_name(ref)) - # Prevent any changes to existing git tag unless user has permissions - :admin_project - else - :push_code - end - unless user.can?(action, project) + unless change_allowed?(user, project, change) # If user does not have access to make at least one change - cancel all push return false end @@ -77,6 +59,29 @@ module Gitlab true end + def change_allowed?(user, project, change) + oldrev, newrev, ref = change.split(' ') + + action = if project.protected_branch?(branch_name(ref)) + # we dont allow force push to protected branch + if forced_push?(project, oldrev, newrev) + :force_push_code_to_protected_branches + # and we dont allow remove of protected branch + elsif newrev =~ /0000000/ + :remove_protected_branches + else + :push_code_to_protected_branches + end + elsif project.repository && project.repository.tag_names.include?(tag_name(ref)) + # Prevent any changes to existing git tag unless user has permissions + :admin_project + else + :push_code + end + + user.can?(action, project) + end + def forced_push?(project, oldrev, newrev) return false if project.empty_repo? diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb new file mode 100644 index 00000000000..9f0eb3be20f --- /dev/null +++ b/lib/gitlab/git_access_wiki.rb @@ -0,0 +1,7 @@ +module Gitlab + class GitAccessWiki < GitAccess + def change_allowed?(user, project, change) + user.can?(:write_wiki, project) + end + end +end diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb index fbfed205a0f..9e02ccc0f44 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/ldap/access.rb b/lib/gitlab/ldap/access.rb index d2235d2e3bc..eb2c4e48ff2 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -1,18 +1,21 @@ +# LDAP authorization model +# +# * Check if we are allowed access (not blocked) +# module Gitlab module LDAP class Access - attr_reader :adapter + attr_reader :adapter, :provider, :user - def self.open(&block) - Gitlab::LDAP::Adapter.open do |adapter| - block.call(self.new(adapter)) + def self.open(user, &block) + Gitlab::LDAP::Adapter.open(user.provider) do |adapter| + block.call(self.new(user, adapter)) end end def self.allowed?(user) - self.open do |access| - if access.allowed?(user) - # GitLab EE LDAP code goes here + self.open(user) do |access| + if access.allowed? user.last_credential_check_at = Time.now user.save true @@ -22,21 +25,30 @@ module Gitlab end end - def initialize(adapter=nil) + def initialize(user, adapter=nil) @adapter = adapter + @user = user + @provider = user.provider end - def allowed?(user) + def allowed? if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter) - if Gitlab.config.ldap.active_directory - !Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter) - end + return true unless ldap_config.active_directory + !Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter) else false end rescue false end + + def adapter + @adapter ||= Gitlab::LDAP::Adapter.new(provider) + end + + def ldap_config + Gitlab::LDAP::Config.new(provider) + end end end end diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index 68ac1b22909..256cdb4c2f1 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -1,55 +1,28 @@ module Gitlab module LDAP class Adapter - attr_reader :ldap + attr_reader :provider, :ldap - def self.open(&block) - Net::LDAP.open(adapter_options) do |ldap| - block.call(self.new(ldap)) + def self.open(provider, &block) + Net::LDAP.open(config(provider).adapter_options) do |ldap| + block.call(self.new(provider, ldap)) end end - def self.config - Gitlab.config.ldap + def self.config(provider) + Gitlab::LDAP::Config.new(provider) end - def self.adapter_options - encryption = - case config['method'].to_s - when 'ssl' - :simple_tls - when 'tls' - :start_tls - else - nil - end - - options = { - host: config['host'], - port: config['port'], - encryption: encryption - } - - auth_options = { - auth: { - method: :simple, - username: config['bind_dn'], - password: config['password'] - } - } - - if config['password'] || config['bind_dn'] - options.merge!(auth_options) - end - options + def initialize(provider, ldap=nil) + @provider = provider + @ldap = ldap || Net::LDAP.new(config.adapter_options) end - - def initialize(ldap=nil) - @ldap = ldap || Net::LDAP.new(self.class.adapter_options) + def config + Gitlab::LDAP::Config.new(provider) end - def users(field, value) + def users(field, value, limit = nil) if field.to_sym == :dn options = { base: value, @@ -57,13 +30,13 @@ module Gitlab } else options = { - base: config['base'], + base: config.base, filter: Net::LDAP::Filter.eq(field, value) } end - if config['user_filter'].present? - user_filter = Net::LDAP::Filter.construct(config['user_filter']) + if config.user_filter.present? + user_filter = Net::LDAP::Filter.construct(config.user_filter) options[:filter] = if options[:filter] Net::LDAP::Filter.join(options[:filter], user_filter) @@ -72,12 +45,16 @@ module Gitlab end end + if limit.present? + options.merge!(size: limit) + end + entries = ldap_search(options).select do |entry| entry.respond_to? config.uid end entries.map do |entry| - Gitlab::LDAP::Person.new(entry) + Gitlab::LDAP::Person.new(entry, provider) end end @@ -105,12 +82,6 @@ module Gitlab results end end - - private - - def config - @config ||= self.class.config - end end end end diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb new file mode 100644 index 00000000000..a5944f96983 --- /dev/null +++ b/lib/gitlab/ldap/authentication.rb @@ -0,0 +1,71 @@ +# This calls helps to authenticate to LDAP by providing username and password +# +# Since multiple LDAP servers are supported, it will loop through all of them +# until a valid bind is found +# + +module Gitlab + module LDAP + class Authentication + def self.login(login, password) + return unless Gitlab::LDAP::Config.enabled? + return unless login.present? && password.present? + + auth = nil + # loop through providers until valid bind + providers.find do |provider| + auth = new(provider) + auth.login(login, password) # true will exit the loop + end + + # If (login, password) was invalid for all providers, the value of auth is now the last + # Gitlab::LDAP::Authentication instance we tried. + auth.user + end + + def self.providers + Gitlab::LDAP::Config.providers + end + + attr_accessor :provider, :ldap_user + + def initialize(provider) + @provider = provider + end + + def login(login, password) + @ldap_user = adapter.bind_as( + filter: user_filter(login), + size: 1, + password: password + ) + end + + def adapter + OmniAuth::LDAP::Adaptor.new(config.options) + end + + def config + Gitlab::LDAP::Config.new(provider) + end + + def user_filter(login) + filter = Net::LDAP::Filter.eq(config.uid, login) + + # Apply LDAP user filter if present + if config.user_filter.present? + filter = Net::LDAP::Filter.join( + filter, + Net::LDAP::Filter.construct(config.user_filter) + ) + end + filter + end + + def user + return nil unless ldap_user + Gitlab::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider) + end + end + end +end
\ No newline at end of file diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb new file mode 100644 index 00000000000..d41bfba9b0f --- /dev/null +++ b/lib/gitlab/ldap/config.rb @@ -0,0 +1,115 @@ +# Load a specific server configuration +module Gitlab + module LDAP + class Config + attr_accessor :provider, :options + + def self.enabled? + Gitlab.config.ldap.enabled + end + + def self.servers + Gitlab.config.ldap.servers.values + end + + def self.providers + servers.map {|server| server['provider_name'] } + end + + def initialize(provider) + @provider = provider + invalid_provider unless valid_provider? + @options = config_for(provider) + end + + def enabled? + base_config.enabled + end + + def adapter_options + { + host: options['host'], + port: options['port'], + encryption: encryption + }.tap do |options| + options.merge!(auth_options) if has_auth? + end + end + + def base + options['base'] + end + + def uid + options['uid'] + end + + def sync_ssh_keys? + sync_ssh_keys.present? + end + + # The LDAP attribute in which the ssh keys are stored + def sync_ssh_keys + options['sync_ssh_keys'] + end + + def user_filter + options['user_filter'] + end + + def group_base + options['group_base'] + end + + def admin_group + options['admin_group'] + end + + def active_directory + options['active_directory'] + end + + protected + def base_config + Gitlab.config.ldap + end + + def config_for(provider) + base_config.servers.values.find { |server| server['provider_name'] == provider } + end + + def encryption + case options['method'].to_s + when 'ssl' + :simple_tls + when 'tls' + :start_tls + else + nil + end + end + + def valid_provider? + self.class.providers.include?(provider) + end + + def invalid_provider + raise "Unknown provider (#{provider}). Available providers: #{self.class.providers}" + end + + def auth_options + { + auth: { + method: :simple, + username: options['bind_dn'], + password: options['password'] + } + } + end + + def has_auth? + options['password'] || options['bind_dn'] + end + end + end +end diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb index 87c3d711db4..3e0b3e6cbf8 100644 --- a/lib/gitlab/ldap/person.rb +++ b/lib/gitlab/ldap/person.rb @@ -6,24 +6,24 @@ module Gitlab # Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/ AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2") - def self.find_by_uid(uid, adapter=nil) - adapter ||= Gitlab::LDAP::Adapter.new - adapter.user(config.uid, uid) + attr_accessor :entry, :provider + + def self.find_by_uid(uid, adapter) + adapter.user(adapter.config.uid, uid) end - def self.find_by_dn(dn, adapter=nil) - adapter ||= Gitlab::LDAP::Adapter.new + def self.find_by_dn(dn, adapter) adapter.user('dn', dn) end - def self.disabled_via_active_directory?(dn, adapter=nil) - adapter ||= Gitlab::LDAP::Adapter.new + def self.disabled_via_active_directory?(dn, adapter) adapter.dn_matches_filter?(dn, AD_USER_DISABLED) end - def initialize(entry) + def initialize(entry, provider) Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" } @entry = entry + @provider = provider end def name @@ -38,6 +38,10 @@ module Gitlab uid end + def email + entry.try(:mail) + end + def dn entry.dn end @@ -48,12 +52,8 @@ module Gitlab @entry end - def adapter - @adapter ||= Gitlab::LDAP::Adapter.new - end - def config - @config ||= Gitlab.config.ldap + @config ||= Gitlab::LDAP::Config.new(provider) end end end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 25b5a702f9a..3176e9790a7 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -10,77 +10,52 @@ module Gitlab module LDAP class User < Gitlab::OAuth::User class << self - def find_or_create(auth_hash) - self.auth_hash = auth_hash - find(auth_hash) || find_and_connect_by_email(auth_hash) || create(auth_hash) - end - - def find_and_connect_by_email(auth_hash) - self.auth_hash = auth_hash - user = model.find_by(email: self.auth_hash.email) - - if user - user.update_attributes(extern_uid: auth_hash.uid, provider: auth_hash.provider) - Gitlab::AppLogger.info("(LDAP) Updating legacy LDAP user #{self.auth_hash.email} with extern_uid => #{auth_hash.uid}") - return user - end - end - - def authenticate(login, password) - # Check user against LDAP backend if user is not authenticated - # Only check with valid login and password to prevent anonymous bind results - return nil unless ldap_conf.enabled && login.present? && password.present? - - ldap_user = adapter.bind_as( - filter: user_filter(login), - size: 1, - password: password - ) - - find_by_uid(ldap_user.dn) if ldap_user - end - - def adapter - @adapter ||= OmniAuth::LDAP::Adaptor.new(ldap_conf) + def find_by_uid_and_provider(uid, provider) + # LDAP distinguished name is case-insensitive + ::User. + where(provider: [provider, :ldap]). + where('lower(extern_uid) = ?', uid.downcase).last end + end - protected - - def find_by_uid_and_provider - find_by_uid(auth_hash.uid) - end + def initialize(auth_hash) + super + update_user_attributes + end - def find_by_uid(uid) - # LDAP distinguished name is case-insensitive - model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last - end + # instance methods + def gl_user + @gl_user ||= find_by_uid_and_provider || find_by_email || build_new_user + end - def provider - 'ldap' - end + def find_by_uid_and_provider + self.class.find_by_uid_and_provider( + auth_hash.uid.downcase, auth_hash.provider) + end - def raise_error(message) - raise OmniAuth::Error, "(LDAP) " + message - end + def find_by_email + model.find_by(email: auth_hash.email) + end - def ldap_conf - Gitlab.config.ldap - end + def update_user_attributes + gl_user.attributes = { + extern_uid: auth_hash.uid, + provider: auth_hash.provider, + email: auth_hash.email + } + end - def user_filter(login) - filter = Net::LDAP::Filter.eq(adapter.uid, login) - # Apply LDAP user filter if present - if ldap_conf['user_filter'].present? - user_filter = Net::LDAP::Filter.construct(ldap_conf['user_filter']) - filter = Net::LDAP::Filter.join(filter, user_filter) - end - filter - end + def changed? + gl_user.changed? end def needs_blocking? false end + + def allowed? + Gitlab::LDAP::Access.allowed?(gl_user) + end end end end diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index 8a73ec5038a..59b21149a9a 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 709a74fe21e..ddcce7557a0 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -33,6 +33,11 @@ module Gitlab attr_reader :html_options + def gfm_with_tasks(text, project = @project, html_options = {}) + text = gfm(text, project, html_options) + parse_tasks(text) + end + # Public: Parse the provided text with GitLab-Flavored Markdown # # text - the source text @@ -65,14 +70,22 @@ module Gitlab insert_piece($1) end - # Context passed to the markdoqwn pipeline + # Used markdown pipelines in GitLab: + # GitlabEmojiFilter - performs emoji replacement. + # + # see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters + filters = [ + HTML::Pipeline::Gitlab::GitlabEmojiFilter + ] + markdown_context = { - asset_root: File.join(root_url, - Gitlab::Application.config.assets.prefix) + asset_root: Gitlab.config.gitlab.url, + asset_host: Gitlab::Application.config.asset_host } - result = HTML::Pipeline::Gitlab::MarkdownPipeline.call(text, - markdown_context) + markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline + + result = markdown_pipeline.call(text, markdown_context) text = result[:output].to_html(save_with: 0) allowed_attributes = ActionView::Base.sanitized_allowed_attributes @@ -265,5 +278,24 @@ module Gitlab ) link_to("#{prefix_text}##{identifier}", url, options) end + + # Turn list items that start with "[ ]" into HTML checkbox inputs. + def parse_tasks(text) + li_tag = '<li class="task-list-item">' + unchecked_box = '<input type="checkbox" value="on" disabled />' + checked_box = unchecked_box.sub(/\/>$/, 'checked="checked" />') + + # Regexp captures don't seem to work when +text+ is an + # ActiveSupport::SafeBuffer, hence the `String.new` + String.new(text).gsub(Taskable::TASK_PATTERN_HTML) do + checked = $LAST_MATCH_INFO[:checked].downcase == 'x' + + if checked + "#{li_tag}#{checked_box}" + else + "#{li_tag}#{unchecked_box}" + end + end + end end end diff --git a/lib/gitlab/markdown_helper.rb b/lib/gitlab/markdown_helper.rb index abed12fe570..5e3cfc0585b 100644 --- a/lib/gitlab/markdown_helper.rb +++ b/lib/gitlab/markdown_helper.rb @@ -21,5 +21,9 @@ module Gitlab def gitlab_markdown?(filename) filename.downcase.end_with?(*%w(.mdown .md .markdown)) end + + def previewable?(filename) + gitlab_markdown?(filename) || markup?(filename) + end end end diff --git a/lib/gitlab/oauth/auth_hash.rb b/lib/gitlab/oauth/auth_hash.rb index 0198f61f427..ce52beec78e 100644 --- a/lib/gitlab/oauth/auth_hash.rb +++ b/lib/gitlab/oauth/auth_hash.rb @@ -21,7 +21,7 @@ module Gitlab end def name - (info.name || full_name).to_s.force_encoding('utf-8') + (info.try(:name) || full_name).to_s.force_encoding('utf-8') end def full_name diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index b768eda185f..47f62153a50 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -6,55 +6,77 @@ module Gitlab module OAuth class User - class << self - attr_reader :auth_hash + attr_accessor :auth_hash, :gl_user - def find(auth_hash) - self.auth_hash = auth_hash - find_by_uid_and_provider - end + def initialize(auth_hash) + self.auth_hash = auth_hash + end - def create(auth_hash) - user = new(auth_hash) - user.save_and_trigger_callbacks - end + def persisted? + gl_user.try(:persisted?) + end - def model - ::User - end + def new? + !persisted? + end + + def valid? + gl_user.try(:valid?) + end + + def save + unauthorized_to_create unless gl_user - def auth_hash=(auth_hash) - @auth_hash = AuthHash.new(auth_hash) + if needs_blocking? + gl_user.save! + gl_user.block + else + gl_user.save! end - protected - def find_by_uid_and_provider - model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last + log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}" + gl_user + rescue ActiveRecord::RecordInvalid => e + log.info "(OAuth) Error saving user: #{gl_user.errors.full_messages}" + return self, e.record.errors + end + + def gl_user + @user ||= find_by_uid_and_provider + + if signup_enabled? + @user ||= build_new_user end + + @user end - # Instance methods - attr_accessor :auth_hash, :user + protected - def initialize(auth_hash) - self.auth_hash = auth_hash - self.user = self.class.model.new(user_attributes) - user.skip_confirmation! + def needs_blocking? + new? && block_after_signup? + end + + def signup_enabled? + Gitlab.config.omniauth.allow_single_sign_on + end + + def block_after_signup? + Gitlab.config.omniauth.block_auto_created_users end def auth_hash=(auth_hash) @auth_hash = AuthHash.new(auth_hash) end - def save_and_trigger_callbacks - user.save! - log.info "(OAuth) Creating user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}" - user.block if needs_blocking? + def find_by_uid_and_provider + model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last + end - user - rescue ActiveRecord::RecordInvalid => e - log.info "(OAuth) Email #{e.record.errors[:email]}. Username #{e.record.errors[:username]}" - return nil, e.record.errors + def build_new_user + model.new(user_attributes).tap do |user| + user.skip_confirmation! + end end def user_attributes @@ -73,12 +95,12 @@ module Gitlab Gitlab::AppLogger end - def raise_error(message) - raise OmniAuth::Error, "(OAuth) " + message + def model + ::User end - def needs_blocking? - Gitlab.config.omniauth['block_auto_created_users'] + def raise_unauthorized_to_create + raise StandardError.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}") end end end diff --git a/lib/gitlab/production_logger.rb b/lib/gitlab/production_logger.rb new file mode 100644 index 00000000000..89ce7144b1b --- /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/sidekiq_logger.rb b/lib/gitlab/sidekiq_logger.rb new file mode 100644 index 00000000000..c1dab87a432 --- /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/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index bb225f1acd8..54d740908d5 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -10,6 +10,17 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML super options end + # If project has issue number 39, apostrophe will be linked in + # regular text to the issue as Redcarpet will convert apostrophe to + # #39; + # We replace apostrophe with right single quote before Redcarpet + # does the processing and put the apostrophe back in postprocessing. + # This only influences regular text, code blocks are untouched. + def normal_text(text) + return text unless text.present? + text.gsub("'", "’") + end + def block_code(code, language) # New lines are placed to fix an rendering issue # with code wrapped inside <h1> tag for next case: @@ -44,9 +55,14 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML end def postprocess(full_document) + full_document.gsub!("’", "'") unless @template.instance_variable_get("@project_wiki") || @project.nil? full_document = h.create_relative_links(full_document) end - h.gfm(full_document) + if @options[:parse_tasks] + h.gfm_with_tasks(full_document) + else + h.gfm(full_document) + end end end diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 5f1afe6575c..fd4f93c2f92 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -19,7 +19,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 @@ -42,7 +42,7 @@ 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 - + ## 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; @@ -65,14 +65,15 @@ server { ssl_certificate /etc/nginx/ssl/gitlab.crt; ssl_certificate_key /etc/nginx/ssl/gitlab.key; - ssl_ciphers 'AES256+EECDH:AES256+EDH'; + # 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; - ## [WARNING] The following header states that the browser should only communicate + ## [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; @@ -90,8 +91,7 @@ server { # resolver_timeout 10s; ## [Optional] Generate a stronger DHE parameter: - ## cd /etc/ssl/certs - ## sudo openssl dhparam -out dhparam.pem 4096 + ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 ## # ssl_dhparam /etc/ssl/certs/dhparam.pem; diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 9ec368254ac..56e8ff44988 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -664,7 +664,7 @@ namespace :gitlab do warn_user_is_not_gitlab start_checking "LDAP" - if ldap_config.enabled + if Gitlab::LDAP::Config.enabled? print_users(args.limit) else puts 'LDAP is disabled in config/gitlab.yml' @@ -675,39 +675,19 @@ namespace :gitlab do def print_users(limit) puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)" - ldap.search(attributes: attributes, filter: filter, size: limit, return_result: false) do |entry| - puts "DN: #{entry.dn}\t#{ldap_config.uid}: #{entry[ldap_config.uid]}" - end - end - - def attributes - [ldap_config.uid] - end - def filter - uid_filter = Net::LDAP::Filter.present?(ldap_config.uid) - if user_filter - Net::LDAP::Filter.join(uid_filter, user_filter) - else - uid_filter - end - end + servers = Gitlab::LDAP::Config.providers - def user_filter - if ldap_config['user_filter'] && ldap_config.user_filter.present? - Net::LDAP::Filter.construct(ldap_config.user_filter) - else - nil + servers.each do |server| + puts "Server: #{server}" + Gitlab::LDAP::Adapter.open(server) do |adapter| + users = adapter.users(adapter.config.uid, '*', 100) + users.each do |user| + puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}" + end + end end end - - def ldap - @ldap ||= OmniAuth::LDAP::Adaptor.new(ldap_config).connection - end - - def ldap_config - @ldap_config ||= Gitlab.config.ldap - end end # Helper methods diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index cbfa736c84c..b6ed874e11a 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -27,7 +27,10 @@ namespace :gitlab do group_name = nil if group_name == '.' # Skip if group or user - next if namespaces.include?(name) + if namespaces.include?(name) + puts "Skipping #{project.name} due to namespace conflict with group or user".yellow + next + end puts "Processing #{repo_path}".yellow diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index a8f26a7c029..c3d1aa0125d 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -11,7 +11,7 @@ namespace :gitlab do home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Settings.gitlab.user_home gitlab_url = Settings.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 diff --git a/spec/factories.rb b/spec/factories.rb index a960571206c..15899d8c3c4 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -24,6 +24,11 @@ FactoryGirl.define do admin true end + trait :ldap do + provider 'ldapmain' + extern_uid 'my-ldap-id' + end + factory :admin, traits: [:admin] end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 524c4d5fa21..98ba5a47ee5 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -10,8 +10,12 @@ describe "Projects", feature: true do visit edit_project_path(@project) end - it "should be correct path" do - expect { click_link "Remove project" }.to change {Project.count}.by(-1) + 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) end end end diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb new file mode 100644 index 00000000000..5af76968183 --- /dev/null +++ b/spec/finders/snippets_finder_spec.rb @@ -0,0 +1,94 @@ +require 'spec_helper' + +describe SnippetsFinder do + let(:user) { create :user } + let(:user1) { create :user } + let(:group) { create :group } + + let(:project1) { create(:empty_project, :public, group: group) } + let(:project2) { create(:empty_project, :private, group: group) } + + + context ':all filter' do + before do + @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE) + @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL) + @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC) + end + + it "returns all private and internal snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :all) + snippets.should include(@snippet2, @snippet3) + snippets.should_not include(@snippet1) + end + + it "returns all public snippets" do + snippets = SnippetsFinder.new.execute(nil, filter: :all) + snippets.should include(@snippet3) + snippets.should_not include(@snippet1, @snippet2) + end + end + + context ':by_user filter' do + before do + @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE, author: user) + @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL, author: user) + @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC, author: user) + end + + it "returns all public and internal snippets" do + snippets = SnippetsFinder.new.execute(user1, filter: :by_user, user: user) + snippets.should include(@snippet2, @snippet3) + snippets.should_not include(@snippet1) + end + + it "returns internal snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_internal") + snippets.should include(@snippet2) + snippets.should_not include(@snippet1, @snippet3) + end + + it "returns private snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_private") + snippets.should include(@snippet1) + snippets.should_not include(@snippet2, @snippet3) + end + + it "returns public snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_public") + snippets.should include(@snippet3) + snippets.should_not include(@snippet1, @snippet2) + end + + it "returns all snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user) + snippets.should include(@snippet1, @snippet2, @snippet3) + end + end + + context 'by_project filter' do + before do + @snippet1 = create(:project_snippet, visibility_level: Snippet::PRIVATE, project: project1) + @snippet2 = create(:project_snippet, visibility_level: Snippet::INTERNAL, project: project1) + @snippet3 = create(:project_snippet, visibility_level: Snippet::PUBLIC, project: project1) + end + + it "returns public snippets for unauthorized user" do + snippets = SnippetsFinder.new.execute(nil, filter: :by_project, project: project1) + snippets.should include(@snippet3) + snippets.should_not include(@snippet1, @snippet2) + end + + it "returns public and internal snippets for none project members" do + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1) + snippets.should include(@snippet2, @snippet3) + snippets.should_not include(@snippet1) + end + + it "returns all snippets for project members" do + project1.team << [user, :developer] + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1) + snippets.should include(@snippet1, @snippet2, @snippet3) + end + end +end diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb new file mode 100644 index 00000000000..4de54d291f2 --- /dev/null +++ b/spec/helpers/events_helper_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe EventsHelper do + include ApplicationHelper + include GitlabMarkdownHelper + + it 'should display one line of plain text without alteration' do + input = 'A short, plain note' + expect(event_note(input)).to match(input) + expect(event_note(input)).not_to match(/\.\.\.\z/) + end + + it 'should display inline code' do + input = 'A note with `inline code`' + expected = 'A note with <code>inline code</code>' + + expect(event_note(input)).to match(expected) + end + + it 'should truncate a note with multiple paragraphs' do + input = "Paragraph 1\n\nParagraph 2" + expected = 'Paragraph 1...' + + expect(event_note(input)).to match(expected) + end + + it 'should display the first line of a code block' do + input = "```\nCode block\nwith two lines\n```" + expected = '<pre><code class="">Code block...</code></pre>' + + expect(event_note(input)).to match(expected) + end + + it 'should truncate a single long line of text' do + text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars + input = "#{text}#{text}#{text}#{text}" # 200 chars + expected = "#{text}#{text}".sub(/.{3}/, '...') + + expect(event_note(input)).to match(expected) + end + + it 'should preserve a link href when link text is truncated' do + text = 'The quick brown fox jumped over the lazy dog' # 44 chars + input = "#{text}#{text}#{text} " # 133 chars + link_url = 'http://example.com/foo/bar/baz' # 30 chars + input << link_url + expected_link_text = 'http://example...</a>' + + expect(event_note(input)).to match(link_url) + expect(event_note(input)).to match(expected_link_text) + end +end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 73b3d91e96e..61751a82369 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -60,7 +60,7 @@ describe GitlabMarkdownHelper do end it "should link using a short id" do - actual = "Backported from #{commit.short_id(6)}" + actual = "Backported from #{commit.short_id}" gfm(actual).should match(expected) end @@ -530,6 +530,24 @@ describe GitlabMarkdownHelper do markdown(actual).should match(%r{<li>light by <a.+>@#{member.user.username}</a></li>}) end + it "should not link the apostrophe to issue 39" do + project.team << [user, :master] + project.issues.stub(:where).with(iid: '39').and_return([issue]) + + actual = "Yes, it is @#{member.user.username}'s task." + expected = /Yes, it is <a.+>@#{member.user.username}<\/a>'s task/ + markdown(actual).should match(expected) + end + + it "should not link the apostrophe to issue 39 in code blocks" do + project.team << [user, :master] + project.issues.stub(:where).with(iid: '39').and_return([issue]) + + actual = "Yes, `it is @#{member.user.username}'s task.`" + expected = /Yes, <code>it is @gfm\'s task.<\/code>/ + markdown(actual).should match(expected) + end + it "should handle references in <em>" do actual = "Apply _!#{merge_request.iid}_ ASAP" @@ -576,9 +594,21 @@ describe GitlabMarkdownHelper do end it "should generate absolute urls for emoji" do - markdown(":smile:").should include("src=\"#{url_helper('emoji/smile')}") + markdown(":smile:").should include("src=\"http://localhost/assets/emoji/smile.png") + end + + it "should generate absolute urls for emoji if relative url is present" do + Gitlab.config.gitlab.stub(:url).and_return('http://localhost/gitlab/root') + markdown(":smile:").should include("src=\"http://localhost/gitlab/root/assets/emoji/smile.png") + end + + it "should generate absolute urls for emoji if asset_host is present" do + Gitlab::Application.config.stub(:asset_host).and_return("https://cdn.example.com") + ActionView::Base.any_instance.stub_chain(:config, :asset_host).and_return("https://cdn.example.com") + markdown(":smile:").should include("src=\"https://cdn.example.com/assets/emoji/smile.png") end + it "should handle relative urls for a file in master" do actual = "[GitLab API doc](doc/api/README.md)\n" expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n" @@ -616,7 +646,7 @@ describe GitlabMarkdownHelper do end end - describe "markdwon for empty repository" do + describe 'markdown for empty repository' do before do @project = empty_project @repository = empty_project.repository @@ -652,4 +682,103 @@ describe GitlabMarkdownHelper do helper.render_wiki_content(@wiki) end end + + describe '#gfm_with_tasks' do + before(:all) do + @source_text_asterisk = <<EOT.gsub(/^\s{8}/, '') + * [ ] valid unchecked task + * [x] valid lowercase checked task + * [X] valid uppercase checked task + * [ ] valid unchecked nested task + * [x] valid checked nested task + + [ ] not an unchecked task - no list item + [x] not a checked task - no list item + + * [ ] not an unchecked task - too many spaces + * [x ] not a checked task - too many spaces + * [] not an unchecked task - no spaces + * Not a task [ ] - not at beginning +EOT + + @source_text_dash = <<EOT.gsub(/^\s{8}/, '') + - [ ] valid unchecked task + - [x] valid lowercase checked task + - [X] valid uppercase checked task + - [ ] valid unchecked nested task + - [x] valid checked nested task +EOT + end + + it 'should render checkboxes at beginning of asterisk list items' do + rendered_text = markdown(@source_text_asterisk, parse_tasks: true) + + expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/) + expect(rendered_text).to match( + /<input.*checkbox.*valid lowercase checked task/ + ) + expect(rendered_text).to match( + /<input.*checkbox.*valid uppercase checked task/ + ) + end + + it 'should render checkboxes at beginning of dash list items' do + rendered_text = markdown(@source_text_dash, parse_tasks: true) + + expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/) + expect(rendered_text).to match( + /<input.*checkbox.*valid lowercase checked task/ + ) + expect(rendered_text).to match( + /<input.*checkbox.*valid uppercase checked task/ + ) + end + + it 'should not be confused by whitespace before bullets' do + rendered_text_asterisk = markdown(@source_text_asterisk, + parse_tasks: true) + rendered_text_dash = markdown(@source_text_dash, parse_tasks: true) + + expect(rendered_text_asterisk).to match( + /<input.*checkbox.*valid unchecked nested task/ + ) + expect(rendered_text_asterisk).to match( + /<input.*checkbox.*valid checked nested task/ + ) + expect(rendered_text_dash).to match( + /<input.*checkbox.*valid unchecked nested task/ + ) + expect(rendered_text_dash).to match( + /<input.*checkbox.*valid checked nested task/ + ) + end + + it 'should not render checkboxes outside of list items' do + rendered_text = markdown(@source_text_asterisk, parse_tasks: true) + + expect(rendered_text).not_to match( + /<input.*checkbox.*not an unchecked task - no list item/ + ) + expect(rendered_text).not_to match( + /<input.*checkbox.*not a checked task - no list item/ + ) + end + + it 'should not render checkboxes with invalid formatting' do + rendered_text = markdown(@source_text_asterisk, parse_tasks: true) + + expect(rendered_text).not_to match( + /<input.*checkbox.*not an unchecked task - too many spaces/ + ) + expect(rendered_text).not_to match( + /<input.*checkbox.*not a checked task - too many spaces/ + ) + expect(rendered_text).not_to match( + /<input.*checkbox.*not an unchecked task - no spaces/ + ) + expect(rendered_text).not_to match( + /Not a task.*<input.*checkbox.*not at beginning/ + ) + end + end end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 551fb3fb5f6..1f3e1a4a3c1 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -28,17 +28,16 @@ describe Gitlab::Auth do end context "with ldap enabled" do - before { Gitlab.config.ldap['enabled'] = true } - after { Gitlab.config.ldap['enabled'] = false } + before { Gitlab::LDAP::Config.stub(enabled?: true) } it "tries to autheticate with db before ldap" do - expect(Gitlab::LDAP::User).not_to receive(:authenticate) + expect(Gitlab::LDAP::Authentication).not_to receive(:login) gl_auth.find(username, password) end it "uses ldap as fallback to for authentication" do - expect(Gitlab::LDAP::User).to receive(:authenticate) + expect(Gitlab::LDAP::Authentication).to receive(:login) gl_auth.find('ldap_user', 'password') end diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb new file mode 100644 index 00000000000..ed5785b31e6 --- /dev/null +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe Gitlab::GitAccessWiki do + let(:access) { Gitlab::GitAccessWiki.new } + let(:project) { create(:project) } + let(:user) { create(:user) } + + describe 'push_allowed?' do + before do + create(:protected_branch, name: 'master', project: project) + project.team << [user, :developer] + end + + subject { access.push_allowed?(user, project, changes) } + + it { should be_true } + end + + def changes + ['6f6d7e7ed 570e7b2ab refs/heads/master'] + end +end diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index d50f605e050..f4d5a927396 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' describe Gitlab::LDAP::Access do - let(:access) { Gitlab::LDAP::Access.new } - let(:user) { create(:user) } + let(:access) { Gitlab::LDAP::Access.new user } + let(:user) { create(:user, :ldap) } describe :allowed? do - subject { access.allowed?(user) } + subject { access.allowed? } context 'when the user cannot be found' do before { Gitlab::LDAP::Person.stub(find_by_dn: nil) } @@ -28,20 +28,14 @@ describe Gitlab::LDAP::Access do it { should be_true } end - context 'and has no disabled flag in active diretory' do - before { - Gitlab::LDAP::Person.stub(disabled_via_active_directory?: false) - Gitlab.config.ldap['enabled'] = true - Gitlab.config.ldap['active_directory'] = false - } - - after { - Gitlab.config.ldap['enabled'] = false - Gitlab.config.ldap['active_directory'] = true - } + context 'without ActiveDirectory enabled' do + before do + Gitlab::LDAP::Config.stub(enabled?: true) + Gitlab::LDAP::Config.any_instance.stub(active_directory: false) + end - it { should be_false } + it { should be_true } end end end -end +end
\ No newline at end of file diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb index c3f07334431..19347e47378 100644 --- a/spec/lib/gitlab/ldap/adapter_spec.rb +++ b/spec/lib/gitlab/ldap/adapter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::LDAP::Adapter do - let(:adapter) { Gitlab::LDAP::Adapter.new } + let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' } describe :dn_matches_filter? do let(:ldap) { double(:ldap) } diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb new file mode 100644 index 00000000000..0eb7c443b8b --- /dev/null +++ b/spec/lib/gitlab/ldap/authentication_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe Gitlab::LDAP::Authentication do + let(:klass) { Gitlab::LDAP::Authentication } + let(:user) { create(:user, :ldap, extern_uid: dn) } + let(:dn) { 'uid=john,ou=people,dc=example,dc=com' } + let(:login) { 'john' } + let(:password) { 'password' } + + describe :login do + let(:adapter) { double :adapter } + before do + Gitlab::LDAP::Config.stub(enabled?: true) + end + + it "finds the user if authentication is successful" do + user + # try only to fake the LDAP call + klass.any_instance.stub(adapter: double(:adapter, + bind_as: double(:ldap_user, dn: dn) + )) + expect(klass.login(login, password)).to be_true + end + + it "is false if the user does not exist" do + # try only to fake the LDAP call + klass.any_instance.stub(adapter: double(:adapter, + bind_as: double(:ldap_user, dn: dn) + )) + expect(klass.login(login, password)).to be_false + end + + it "is false if authentication fails" do + user + # try only to fake the LDAP call + klass.any_instance.stub(adapter: double(:adapter, bind_as: nil)) + expect(klass.login(login, password)).to be_false + end + + it "fails if ldap is disabled" do + Gitlab::LDAP::Config.stub(enabled?: false) + expect(klass.login(login, password)).to be_false + end + + it "fails if no login is supplied" do + expect(klass.login('', password)).to be_false + end + + it "fails if no password is supplied" do + expect(klass.login(login, '')).to be_false + end + end +end
\ No newline at end of file diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb new file mode 100644 index 00000000000..76cc7f95c47 --- /dev/null +++ b/spec/lib/gitlab/ldap/config_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::LDAP::Config do + let(:config) { Gitlab::LDAP::Config.new provider } + let(:provider) { 'ldapmain' } + + describe :initalize do + it 'requires a provider' do + expect{ Gitlab::LDAP::Config.new }.to raise_error ArgumentError + end + + it "works" do + expect(config).to be_a described_class + end + + it "raises an error if a unknow provider is used" do + expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error + end + end +end
\ No newline at end of file diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index d232cb20759..726c9764e3d 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -1,54 +1,36 @@ require 'spec_helper' describe Gitlab::LDAP::User do - let(:gl_user) { Gitlab::LDAP::User } + let(:gl_user) { Gitlab::LDAP::User.new(auth_hash) } let(:info) do - double( + { name: 'John', email: 'john@example.com', nickname: 'john' - ) + } + end + let(:auth_hash) do + double(uid: 'my-uid', provider: 'ldapmain', info: double(info)) end - before { Gitlab.config.stub(omniauth: {}) } describe :find_or_create do - let(:auth) do - double(info: info, provider: 'ldap', uid: 'my-uid') - end - it "finds the user if already existing" do - existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldap') + existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldapmain') - expect{ gl_user.find_or_create(auth) }.to_not change{ User.count } + expect{ gl_user.save }.to_not change{ User.count } end it "connects to existing non-ldap user if the email matches" do existing_user = create(:user, email: 'john@example.com') - expect{ gl_user.find_or_create(auth) }.to_not change{ User.count } + expect{ gl_user.save }.to_not change{ User.count } existing_user.reload expect(existing_user.extern_uid).to eql 'my-uid' - expect(existing_user.provider).to eql 'ldap' + expect(existing_user.provider).to eql 'ldapmain' end it "creates a new user if not found" do - expect{ gl_user.find_or_create(auth) }.to change{ User.count }.by(1) - end - end - - describe "authenticate" do - let(:login) { 'john' } - let(:password) { 'my-secret' } - - before { - Gitlab.config.ldap['enabled'] = true - Gitlab.config.ldap['user_filter'] = 'employeeType=developer' - } - after { Gitlab.config.ldap['enabled'] = false } - - it "send an authentication request to ldap" do - expect( Gitlab::LDAP::User.adapter ).to receive(:bind_as) - Gitlab::LDAP::User.authenticate(login, password) + expect{ gl_user.save }.to change{ User.count }.by(1) end end end diff --git a/spec/lib/gitlab/oauth/auth_hash_spec.rb b/spec/lib/gitlab/oauth/auth_hash_spec.rb new file mode 100644 index 00000000000..5eb77b492b2 --- /dev/null +++ b/spec/lib/gitlab/oauth/auth_hash_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Gitlab::OAuth::AuthHash do + let(:auth_hash) do + Gitlab::OAuth::AuthHash.new(double({ + provider: 'twitter', + uid: uid, + info: double(info_hash) + })) + end + let(:uid) { 'my-uid' } + let(:email) { 'my-email@example.com' } + let(:nickname) { 'my-nickname' } + let(:info_hash) { + { + email: email, + nickname: nickname, + name: 'John', + first_name: "John", + last_name: "Who" + } + } + + context "defaults" do + it { expect(auth_hash.provider).to eql 'twitter' } + it { expect(auth_hash.uid).to eql uid } + it { expect(auth_hash.email).to eql email } + it { expect(auth_hash.username).to eql nickname } + it { expect(auth_hash.name).to eql "John" } + it { expect(auth_hash.password).to_not be_empty } + end + + context "email not provided" do + before { info_hash.delete(:email) } + it "generates a temp email" do + expect( auth_hash.email).to start_with('temp-email-for-oauth') + end + end + + context "username not provided" do + before { info_hash.delete(:nickname) } + + it "takes the first part of the email as username" do + expect( auth_hash.username ).to eql "my-email" + end + end + + context "name not provided" do + before { info_hash.delete(:name) } + + it "concats first and lastname as the name" do + expect( auth_hash.name ).to eql "John Who" + end + end +end
\ No newline at end of file diff --git a/spec/lib/gitlab/oauth/user_spec.rb b/spec/lib/gitlab/oauth/user_spec.rb index c241e198609..8a83a1b2588 100644 --- a/spec/lib/gitlab/oauth/user_spec.rb +++ b/spec/lib/gitlab/oauth/user_spec.rb @@ -1,83 +1,108 @@ require 'spec_helper' describe Gitlab::OAuth::User do - let(:gl_auth) { Gitlab::OAuth::User } - let(:info) do - double( + let(:oauth_user) { Gitlab::OAuth::User.new(auth_hash) } + let(:gl_user) { oauth_user.gl_user } + let(:uid) { 'my-uid' } + let(:provider) { 'my-provider' } + let(:auth_hash) { double(uid: uid, provider: provider, info: double(info_hash)) } + let(:info_hash) do + { nickname: 'john', name: 'John', email: 'john@mail.com' - ) + } end - before do - Gitlab.config.stub(omniauth: {}) - end - - describe :find do + describe :persisted? do let!(:existing_user) { create(:user, extern_uid: 'my-uid', provider: 'my-provider') } it "finds an existing user based on uid and provider (facebook)" do auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider') - assert gl_auth.find(auth) + expect( oauth_user.persisted? ).to be_true end - it "finds an existing user based on nested uid and provider" do - auth = double(info: info, uid: 'my-uid', provider: 'my-provider') - assert gl_auth.find(auth) + it "returns false if use is not found in database" do + auth_hash.stub(uid: 'non-existing') + expect( oauth_user.persisted? ).to be_false end end - describe :create do - it "should create user from LDAP" do - auth = double(info: info, uid: 'my-uid', provider: 'ldap') - user = gl_auth.create(auth) + describe :save do + let(:provider) { 'twitter' } - user.should be_valid - user.extern_uid.should == auth.uid - user.provider.should == 'ldap' - end + describe 'signup' do + context "with allow_single_sign_on enabled" do + before { Gitlab.config.omniauth.stub allow_single_sign_on: true } - it "should create user from Omniauth" do - auth = double(info: info, uid: 'my-uid', provider: 'twitter') - user = gl_auth.create(auth) + it "creates a user from Omniauth" do + oauth_user.save - user.should be_valid - user.extern_uid.should == auth.uid - user.provider.should == 'twitter' + expect(gl_user).to be_valid + expect(gl_user.extern_uid).to eql uid + expect(gl_user.provider).to eql 'twitter' + end + end + + context "with allow_single_sign_on disabled (Default)" do + it "throws an error" do + expect{ oauth_user.save }.to raise_error StandardError + end + end end - it "should apply defaults to user" do - auth = double(info: info, uid: 'my-uid', provider: 'ldap') - user = gl_auth.create(auth) + describe 'blocking' do + let(:provider) { 'twitter' } + before { Gitlab.config.omniauth.stub allow_single_sign_on: true } - user.should be_valid - user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit - user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group - end + context 'signup' do + context 'dont block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: false } - it "Set a temp email address if not provided (like twitter does)" do - info = double( - uid: 'my-uid', - nickname: 'john', - name: 'John' - ) - auth = double(info: info, uid: 'my-uid', provider: 'my-provider') + it do + oauth_user.save + gl_user.should be_valid + gl_user.should_not be_blocked + end + end - user = gl_auth.create(auth) - expect(user.email).to_not be_empty - end + context 'block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: true } + + it do + oauth_user.save + gl_user.should be_valid + gl_user.should be_blocked + end + end + end + + context 'sign-in' do + before do + oauth_user.save + oauth_user.gl_user.activate + end + + context 'dont block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: false } + + it do + oauth_user.save + gl_user.should be_valid + gl_user.should_not be_blocked + end + end - it 'generates a username if non provided (google)' do - info = double( - uid: 'my-uid', - name: 'John', - email: 'john@example.com' - ) - auth = double(info: info, uid: 'my-uid', provider: 'my-provider') + context 'block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: true } - user = gl_auth.create(auth) - expect(user.username).to eql 'john' + it do + oauth_user.save + gl_user.should be_valid + gl_user.should_not be_blocked + end + end + end end end end diff --git a/spec/models/assembla_service_spec.rb b/spec/models/assembla_service_spec.rb index 0ef475b87c3..4300090eb13 100644 --- a/spec/models/assembla_service_spec.rb +++ b/spec/models/assembla_service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' diff --git a/spec/models/buildbox_service_spec.rb b/spec/models/buildbox_service_spec.rb new file mode 100644 index 00000000000..1d9ca51be16 --- /dev/null +++ b/spec/models/buildbox_service_spec.rb @@ -0,0 +1,73 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +require 'spec_helper' + +describe BuildboxService do + describe 'Associations' do + it { should belong_to :project } + it { should have_one :service_hook } + end + + describe 'commits methods' do + before do + @project = Project.new + @project.stub( + default_branch: 'default-brancho' + ) + + @service = BuildboxService.new + @service.stub( + project: @project, + service_hook: true, + project_url: 'https://buildbox.io/account-name/example-project', + token: 'secret-sauce-webhook-token:secret-sauce-status-token' + ) + end + + describe :webhook_url do + it 'returns the webhook url' do + @service.webhook_url.should == + 'https://webhook.buildbox.io/deliver/secret-sauce-webhook-token' + end + end + + describe :commit_status_path do + it 'returns the correct status page' do + @service.commit_status_path('2ab7834c').should == + 'https://gitlab.buildbox.io/status/secret-sauce-status-token.json?commit=2ab7834c' + end + end + + describe :build_page do + it 'returns the correct build page' do + @service.build_page('2ab7834c').should == + 'https://buildbox.io/account-name/example-project/builds?commit=2ab7834c' + end + end + + describe :builds_page do + it 'returns the correct path to the builds page' do + @service.builds_path.should == + 'https://buildbox.io/account-name/example-project/builds?branch=default-brancho' + end + end + + describe :status_img_path do + it 'returns the correct path to the status image' do + @service.status_img_path.should == 'https://badge.buildbox.io/secret-sauce-status-token.svg' + end + end + end +end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 6f201adc4e8..a6ec44da4be 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -75,7 +75,7 @@ eos it_behaves_like 'a mentionable' do let(:subject) { commit } let(:mauthor) { create :user, email: commit.author_email } - let(:backref_text) { "commit #{subject.sha[0..5]}" } + let(:backref_text) { "commit #{subject.id}" } let(:set_mentionable_text) { ->(txt){ subject.stub(safe_message: txt) } } # Include the subject in the repository stub. diff --git a/spec/models/flowdock_service_spec.rb b/spec/models/flowdock_service_spec.rb index 710b8cba502..5540f0fa988 100644 --- a/spec/models/flowdock_service_spec.rb +++ b/spec/models/flowdock_service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' diff --git a/spec/models/gemnasium_service_spec.rb b/spec/models/gemnasium_service_spec.rb index 5de645cdf33..60ffa6f8b05 100644 --- a/spec/models/gemnasium_service_spec.rb +++ b/spec/models/gemnasium_service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' diff --git a/spec/models/gitlab_ci_service_spec.rb b/spec/models/gitlab_ci_service_spec.rb index e4cd8bb90c3..ebc377047be 100644 --- a/spec/models/gitlab_ci_service_spec.rb +++ b/spec/models/gitlab_ci_service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' diff --git a/spec/models/group_member_spec.rb b/spec/models/group_member_spec.rb index 6acbc9bb4ae..38657de6793 100644 --- a/spec/models/group_member_spec.rb +++ b/spec/models/group_member_spec.rb @@ -1,14 +1,16 @@ # == Schema Information # -# Table name: group_members +# Table name: members # # id :integer not null, primary key # access_level :integer not null -# group_id :integer not null +# source_id :integer not null +# source_type :string(255) not null # user_id :integer not null +# notification_level :integer not null +# type :string(255) # created_at :datetime # updated_at :datetime -# notification_level :integer default(3), not null # require 'spec_helper' diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 8b299cea67c..6b6efe832e5 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -60,4 +60,8 @@ describe Issue do let(:backref_text) { "issue ##{subject.iid}" } let(:set_mentionable_text) { ->(txt){ subject.description = txt } } end + + it_behaves_like 'a Taskable' do + let(:subject) { create :issue } + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c40f75290ed..7b0d261d72f 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -119,4 +119,8 @@ describe MergeRequest do let(:backref_text) { "merge request !#{subject.iid}" } let(:set_mentionable_text) { ->(txt){ subject.title = txt } } end + + it_behaves_like 'a Taskable' do + let(:subject) { create :merge_request, :simple } + end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index c88a03beb0c..2d839e9611b 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -228,7 +228,7 @@ describe Note do it { should be_valid } its(:noteable) { should == issue } - its(:note) { should == "_mentioned in commit #{commit.sha[0..5]}_" } + its(:note) { should == "_mentioned in commit #{commit.sha}_" } end context 'merge request from an issue' do @@ -258,6 +258,17 @@ describe Note do its(:commit_id) { should == commit.id } its(:note) { should == "_mentioned in issue ##{issue.iid}_" } end + + context 'commit from commit' do + let(:parent_commit) { commit.parents.first } + subject { Note.create_cross_reference_note(commit, parent_commit, author, project) } + + it { should be_valid } + its(:noteable_type) { should == "Commit" } + its(:noteable_id) { should be_nil } + its(:commit_id) { should == commit.id } + its(:note) { should == "_mentioned in commit #{parent_commit.id}_" } + end end describe '#cross_reference_exists?' do @@ -278,6 +289,15 @@ describe Note do it 'detects if a mentionable has not already been mentioned' do Note.cross_reference_exists?(issue, commit1).should be_false end + + context 'commit on commit' do + before do + Note.create_cross_reference_note(commit0, commit1, author, project) + end + + it { Note.cross_reference_exists?(commit0, commit1).should be_true } + it { Note.cross_reference_exists?(commit1, commit0).should be_false } + end end describe '#system?' do diff --git a/spec/models/project_member_spec.rb b/spec/models/project_member_spec.rb index 0178d065e57..9b5f89b6d7d 100644 --- a/spec/models/project_member_spec.rb +++ b/spec/models/project_member_spec.rb @@ -1,14 +1,16 @@ # == Schema Information # -# Table name: project_members +# Table name: members # # id :integer not null, primary key +# access_level :integer not null +# source_id :integer not null +# source_type :string(255) not null # user_id :integer not null -# project_id :integer not null +# notification_level :integer not null +# type :string(255) # created_at :datetime # updated_at :datetime -# project_access :integer default(0), not null -# notification_level :integer default(3), not null # require 'spec_helper' diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb index e4df934460b..a6e1d9eef50 100644 --- a/spec/models/project_snippet_spec.rb +++ b/spec/models/project_snippet_spec.rb @@ -2,17 +2,17 @@ # # Table name: snippets # -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# id :integer not null, primary key +# title :string(255) +# content :text +# author_id :integer not null +# project_id :integer +# created_at :datetime +# updated_at :datetime +# file_name :string(255) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # require 'spec_helper' diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 34c1a686c96..bbf50b654f4 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -27,6 +27,8 @@ describe ProjectTeam do it { project.team.master?(guest).should be_false } it { project.team.master?(reporter).should be_false } it { project.team.master?(nonmember).should be_false } + it { project.team.member?(nonmember).should be_false } + it { project.team.member?(guest).should be_true } end end @@ -60,6 +62,8 @@ describe ProjectTeam do it { project.team.master?(guest).should be_true } it { project.team.master?(reporter).should be_false } it { project.team.master?(nonmember).should be_false } + it { project.team.member?(nonmember).should be_false } + it { project.team.member?(guest).should be_true } end end end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 480aeabf67f..c96f2b20529 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb index 4576913b473..526165e397c 100644 --- a/spec/models/slack_service_spec.rb +++ b/spec/models/slack_service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' @@ -26,31 +26,28 @@ describe SlackService do subject.active = true end - it { should validate_presence_of :room } - it { should validate_presence_of :subdomain } - it { should validate_presence_of :token } + it { should validate_presence_of :webhook } end end describe "Execute" do let(:slack) { SlackService.new } + let(:slack_service) { SlackService.new } let(:user) { create(:user) } let(:project) { create(:project) } let(:sample_data) { GitPushService.new.sample_data(project, user) } - let(:subdomain) { 'gitlab' } - let(:token) { 'verySecret' } + let(:webhook) { 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' } + let(:new_webhook) { 'https://hooks.gitlabhq.slack.com/services/cdIj4r4LfXUOySDUjp0tk3OI' } let(:api_url) { - "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{token}" + 'https://gitlabhq.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI' } before do slack.stub( project: project, project_id: project.id, - room: '#gitlab', service_hook: true, - subdomain: subdomain, - token: token + webhook: webhook ) WebMock.stub_request(:post, api_url) @@ -61,5 +58,44 @@ describe SlackService do 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 + end end end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index d179e9516e2..1ef2c512c1f 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -2,17 +2,17 @@ # # Table name: snippets # -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# id :integer not null, primary key +# title :string(255) +# content :text +# author_id :integer not null +# project_id :integer +# created_at :datetime +# updated_at :datetime +# file_name :string(255) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # require 'spec_helper' diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0250014bc21..6ad57b06e06 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -346,6 +346,25 @@ describe User do end end + describe :ldap_user? do + let(:user) { build(:user, :ldap) } + + it "is true if provider name starts with ldap" do + user.provider = 'ldapmain' + expect( user.ldap_user? ).to be_true + end + + it "is false for other providers" do + user.provider = 'other-provider' + expect( user.ldap_user? ).to be_false + end + + it "is false if no extern_uid is provided" do + user.extern_uid = nil + expect( user.ldap_user? ).to be_false + end + end + describe '#full_website_url' do let(:user) { create(:user) } @@ -429,4 +448,32 @@ describe User do expect(user.starred?(project)).to be_false end end + + describe "#sort" do + before do + User.delete_all + @user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha' + @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega' + end + + it "sorts users as recently_signed_in" do + User.sort('recent_sign_in').first.should == @user + end + + it "sorts users as late_signed_in" do + User.sort('oldest_sign_in').first.should == @user1 + end + + it "sorts users as recently_created" do + User.sort('recently_created').first.should == @user + end + + it "sorts users as late_created" do + User.sort('late_created').first.should == @user1 + end + + it "sorts users by name when nil is passed" do + User.sort(nil).first.should == @user + end + end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 6df5ef38961..677b1494041 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -5,10 +5,11 @@ describe API::API, api: true do let(:user) { create(:user) } let(:key) { create(:key, user: user) } let(:project) { create(:project) } + let(:secret_token) { File.read Rails.root.join('.gitlab_shell_secret') } describe "GET /internal/check", no_db: true do it do - get api("/internal/check") + get api("/internal/check"), secret_token: secret_token response.status.should == 200 json_response['api_version'].should == API::API.version @@ -17,7 +18,7 @@ describe API::API, api: true do describe "GET /internal/discover" do it do - get(api("/internal/discover"), key_id: key.id) + get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) response.status.should == 200 @@ -159,7 +160,8 @@ describe API::API, api: true do api("/internal/allowed"), key_id: key.id, project: project.path_with_namespace, - action: 'git-upload-pack' + action: 'git-upload-pack', + secret_token: secret_token ) end @@ -169,7 +171,8 @@ describe API::API, api: true do changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master', key_id: key.id, project: project.path_with_namespace, - action: 'git-receive-pack' + action: 'git-receive-pack', + secret_token: secret_token ) end @@ -179,7 +182,8 @@ describe API::API, api: true do ref: 'master', key_id: key.id, project: project.path_with_namespace, - action: 'git-upload-archive' + action: 'git-upload-archive', + secret_token: secret_token ) end end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index f883c9e028a..d8282d0696b 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/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index 692834c9f29..ebd74206699 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -30,15 +30,15 @@ def common_mentionable_setup "!#{mentioned_mr.iid}, " + "#{ext_proj.path_with_namespace}##{ext_issue.iid}, " + "#{ext_proj.path_with_namespace}!#{ext_mr.iid}, " + - "#{ext_proj.path_with_namespace}@#{ext_commit.id[0..5]}, " + - "#{mentioned_commit.sha[0..5]} and itself as #{backref_text}" + "#{ext_proj.path_with_namespace}@#{ext_commit.short_id}, " + + "#{mentioned_commit.sha[0..10]} and itself as #{backref_text}" end before do # Wire the project's repository to return the mentioned commit, and +nil+ for any # unrecognized commits. - commitmap = { '123456' => mentioned_commit } - extra_commits.each { |c| commitmap[c.sha[0..5]] = c } + commitmap = { '1234567890a' => mentioned_commit } + extra_commits.each { |c| commitmap[c.short_id] = c } mproject.repository.stub(:commit) { |sha| commitmap[sha] } set_mentionable_text.call(ref_string) end @@ -54,7 +54,6 @@ shared_examples 'a mentionable' do it "extracts references from its reference property" do # De-duplicate and omit itself refs = subject.references(mproject) - refs.should have(6).items refs.should include(mentioned_issue) refs.should include(mentioned_mr) @@ -90,7 +89,7 @@ shared_examples 'an editable mentionable' do it 'creates new cross-reference notes when the mentionable text is edited' do new_text = "still mentions ##{mentioned_issue.iid}, " + - "#{mentioned_commit.sha[0..5]}, " + + "#{mentioned_commit.sha[0..10]}, " + "#{ext_issue.iid}, " + "new refs: ##{other_issue.iid}, " + "#{ext_proj.path_with_namespace}##{other_ext_issue.iid}" diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/taskable_shared_examples.rb new file mode 100644 index 00000000000..42252675683 --- /dev/null +++ b/spec/support/taskable_shared_examples.rb @@ -0,0 +1,42 @@ +# Specs for task state functionality for issues and merge requests. +# +# Requires a context containing: +# let(:subject) { Issue or MergeRequest } +shared_examples 'a Taskable' do + before do + subject.description = <<EOT.gsub(/ {6}/, '') + * [ ] Task 1 + * [x] Task 2 + * [x] Task 3 + * [ ] Task 4 + * [ ] Task 5 +EOT + end + + it 'updates the Nth task correctly' do + subject.update_nth_task(1, true) + expect(subject.description).to match(/\[x\] Task 1/) + + subject.update_nth_task(2, true) + expect(subject.description).to match('\[x\] Task 2') + + subject.update_nth_task(3, false) + expect(subject.description).to match('\[ \] Task 3') + + subject.update_nth_task(4, false) + expect(subject.description).to match('\[ \] Task 4') + end + + it 'returns the correct task status' do + expect(subject.task_status).to match('5 tasks') + expect(subject.task_status).to match('2 done') + expect(subject.task_status).to match('3 unfinished') + end + + it 'knows if it has tasks' do + expect(subject.tasks?).to be_true + + subject.description = 'Now I have no tasks' + expect(subject.tasks?).to be_false + end +end |
