diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ac8390074f4c82a6c40960a892a69e4c871eaf1e..2dc09e4a1a5383f49f2ae75ef1638a41ed53006c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,13 @@ -# This file is generated by GitLab CI +image: "ruby:2.1" + +services: + - mysql:latest + - postgres:latest + - redis:latest + +variables: + MYSQL_ALLOW_EMPTY_PASSWORD: "1" + before_script: - ./scripts/prepare_build.sh - ruby -v diff --git a/CHANGELOG b/CHANGELOG index 9490540f1b12bd48581631d45814f93e9628e400..0c4d90855a524f462fec0dd33e82c29431ca3170 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,8 +3,10 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.5.0 (unreleased) - Add "visibility" flag to GET /projects api endpoint - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push + - New UI for pagination + - Fix diff comments loaded by AJAX to load comment with diff in discussion tab -v 8.4.0 (unreleased) +v 8.4.0 - Allow LDAP users to change their email if it was not set by the LDAP server - Ensure Gravatar host looks like an actual host - Consider re-assign as a mention from a notification point of view @@ -14,6 +16,7 @@ v 8.4.0 (unreleased) - Autocomplete data is now always loaded, instead of when focusing a comment text area - Improved performance of finding issues for an entire group - Added custom application performance measuring system powered by InfluxDB + - Add syntax highlighting to diffs - Gracefully handle invalid UTF-8 sequences in Markdown links (Stan Hu) - Bump fog to 1.36.0 (Stan Hu) - Add user's last used IP addresses to admin page (Stan Hu) @@ -74,6 +77,7 @@ v 8.4.0 (unreleased) - Fix: Creator should be added as a master of the project on creation - Added X-GitLab-... headers to emails from CI and Email On Push services (Anton Baklanov) - Add IP check against DNSBLs at account sign-up + - Added cache:key to .gitlab-ci.yml allowing to fine tune the caching v 8.3.4 - Use gitlab-workhorse 0.5.4 (fixes API routing bug) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index a918a2aa18d5bec6a8bb93891a7a63c243111796..ee6cdce3c29053ac99607147be5be250efa001bd 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.6.0 +0.6.1 diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 diff --git a/app/assets/fonts/OFL.txt b/app/assets/fonts/OFL.txt old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-Black.ttf.woff b/app/assets/fonts/SourceSansPro-Black.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-Black.ttf.woff2 b/app/assets/fonts/SourceSansPro-Black.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf.woff b/app/assets/fonts/SourceSansPro-Bold.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-It.ttf.woff b/app/assets/fonts/SourceSansPro-It.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-It.ttf.woff2 b/app/assets/fonts/SourceSansPro-It.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-Light.ttf.woff b/app/assets/fonts/SourceSansPro-Light.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-Light.ttf.woff2 b/app/assets/fonts/SourceSansPro-Light.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf.woff b/app/assets/fonts/SourceSansPro-Regular.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 b/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff old mode 100755 new mode 100644 diff --git a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 old mode 100755 new mode 100644 diff --git a/app/assets/javascripts/build_artifacts.js.coffee b/app/assets/javascripts/build_artifacts.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..5ae6cba56c8ee020e69da29806799b3b25569482 --- /dev/null +++ b/app/assets/javascripts/build_artifacts.js.coffee @@ -0,0 +1,14 @@ +class @BuildArtifacts + constructor: () -> + @disablePropagation() + @setupEntryClick() + + disablePropagation: -> + $('.top-block').on 'click', '.download', (e) -> + e.stopPropagation() + $('.tree-holder').on 'click', 'tr[data-link] a', (e) -> + e.stopImmediatePropagation() + + setupEntryClick: -> + $('.tree-holder').on 'click', 'tr[data-link]', (e) -> + window.location = @dataset.link diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 0d88e8d254a60e1424fa0bb9ae4455c95ee3179f..2cdf01d874cda3e2632d6ff0372dcf3d45a67947 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -100,6 +100,8 @@ class Dispatcher shortcut_handler = true when 'projects:forks:new' new ProjectFork() + when 'projects:artifacts:browse' + new BuildArtifacts() when 'users:show' new User() new Activities() diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 2bfc5cb2d9c556de76b431060377b3e810013d86..53d72be66e3d6286d742bf7b6ba64fe7a6aeaf5c 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -15,6 +15,8 @@ class @Notes @last_fetched_at = last_fetched_at @view = view @noteable_url = document.URL + @notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge") + @initRefresh() @setupMainTargetNoteForm() @cleanBinding() @@ -89,7 +91,7 @@ class @Notes , 15000 refresh: -> - unless document.hidden or (@noteable_url != document.URL) + if not document.hidden and document.URL.indexOf(@noteable_url) is 0 @getContent() getContent: -> @@ -101,7 +103,10 @@ class @Notes notes = data.notes @last_fetched_at = data.last_fetched_at $.each notes, (i, note) => - @renderNote(note) + if note.discussion_with_diff_html? + @renderDiscussionNote(note) + else + @renderNote(note) ### @@ -116,18 +121,21 @@ class @Notes flash.pinTo('.header-content') return + if note.award + awards_handler.addAwardToEmojiBar(note.note) + awards_handler.scrollToAwards() + # render note if it not present in loaded list # or skip if rendered - if @isNewNote(note) && !note.award + else if @isNewNote(note) @note_ids.push(note.id) - $('ul.main-notes-list'). - append(note.html). - syntaxHighlight() + + $('ul.main-notes-list') + .append(note.html) + .syntaxHighlight() @initTaskList() + @updateNotesCount(1) - if note.award - awards_handler.addAwardToEmojiBar(note.note) - awards_handler.scrollToAwards() ### Check if note does not exists on page @@ -144,34 +152,39 @@ class @Notes Note: for rendering inline notes use renderDiscussionNote ### renderDiscussionNote: (note) -> + return unless @isNewNote(note) + @note_ids.push(note.id) - form = $("form[rel='" + note.discussion_id + "']") + form = $("#new-discussion-note-form-#{note.discussion_id}") row = form.closest("tr") note_html = $(note.html) note_html.syntaxHighlight() # is this the first note of discussion? - if row.is(".js-temp-notes-holder") + discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']") + if discussionContainer.length is 0 # insert the note and the reply button after the temp row row.after note.discussion_html # remove the note (will be added again below) row.next().find(".note").remove() + # Before that, the container didn't exist + discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']") + # Add note to 'Changes' page discussions - $(".notes[rel='" + note.discussion_id + "']").append note_html + discussionContainer.append note_html # Init discussion on 'Discussion' page if it is merge request page - if $('body').attr('data-page').indexOf('projects:merge_request') == 0 - discussion_html = $(note.discussion_with_diff_html) - discussion_html.syntaxHighlight() - $('ul.main-notes-list').append(discussion_html) + if $('body').attr('data-page').indexOf('projects:merge_request') is 0 + $('ul.main-notes-list') + .append(note.discussion_with_diff_html) + .syntaxHighlight() else # append new note to all matching discussions - $(".notes[rel='" + note.discussion_id + "']").append note_html + discussionContainer.append note_html - # cleanup after successfully creating a diff/discussion note - @removeDiscussionNoteForm(form) + @updateNotesCount(1) ### Called in response the main target form has been successfully submitted. @@ -278,6 +291,9 @@ class @Notes addDiscussionNote: (xhr, note, status) => @renderDiscussionNote(note) + # cleanup after successfully creating a diff/discussion note + @removeDiscussionNoteForm($("#new-discussion-note-form-#{note.discussion_id}")) + ### Called in response to the edit note form being submitted @@ -349,30 +365,32 @@ class @Notes Removes the actual note from view. Removes the whole discussion if the last note is being removed. ### - removeNote: -> - note = $(this).closest(".note") - note_id = note.attr('id') + removeNote: (e) => + noteId = $(e.currentTarget) + .closest(".note") + .attr("id") - $('.note[id="' + note_id + '"]').each -> - note = $(this) + # A same note appears in the "Discussion" and in the "Changes" tab, we have + # to remove all. Using $(".note[id='noteId']") ensure we get all the notes, + # where $("#noteId") would return only one. + $(".note[id='#{noteId}']").each (i, el) => + note = $(el) notes = note.closest(".notes") - count = notes.closest(".issuable-details").find(".notes-tab .badge") # check if this is the last note for this line if notes.find(".note").length is 1 - # for discussions - notes.closest(".discussion").remove() + # "Discussions" tab + notes.closest(".timeline-entry").remove() - # for diff lines + # "Changes" tab / commit view notes.closest("tr").remove() - # update notes count - oldNum = parseInt(count.text()) - count.text(oldNum - 1) - note.remove() + # Decrement the "Discussions" counter only once + @updateNotesCount(-1) + ### Called in response to clicking the delete attachment link @@ -412,7 +430,7 @@ class @Notes ### setupDiscussionNoteForm: (dataHolder, form) => # setup note target - form.attr "rel", dataHolder.data("discussionId") + form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}" form.find("#line_type").val dataHolder.data("lineType") form.find("#note_commit_id").val dataHolder.data("commitId") form.find("#note_line_code").val dataHolder.data("lineCode") @@ -542,3 +560,6 @@ class @Notes updateTaskList: -> $('form', this).submit() + + updateNotesCount: (updateCount) -> + @notesCountBadge.text(parseInt(@notesCountBadge.text()) + updateCount) diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 585a9d8391336afeb14b3d0e7f31444b88b2effe..9bc814cfd2d6c20f8ab3125a88d9ebb4cb18a92e 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -120,14 +120,6 @@ span.update-author { display: inline; } -.line_holder { - &:hover { - td { - background: #FFFFCF !important; - } - } -} - p.time { color: #999; font-size: 90%; diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 6ee104ee31a575731680d569c7fcd38d22d6a5be..a4791cf6b34ab370c068e73ee1f992c1f5b2515f 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -92,15 +92,6 @@ &:last-child { border-right: none; } - background: #fff; - } - .lines { - pre { - padding: 0; - margin: 0; - background: none; - border: none; - } } img.avatar { border: 0 none; @@ -116,18 +107,18 @@ color: #888; } } - td.blame-numbers { - pre { - color: #AAA; - white-space: pre; - } - background: #f1f1f1; + td.line-numbers { + float: none; border-left: 1px solid #DDD; } td.lines { + padding: 0; code { font-family: $monospace_font; } + pre { + margin: 0; + } } } diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss index 871b808bad4c4aca098f8aacfae64d3160dfba1f..d6cd78813c0aa164a41f9ee92b0b305ef7fbc5f2 100644 --- a/app/assets/stylesheets/framework/jquery.scss +++ b/app/assets/stylesheets/framework/jquery.scss @@ -53,3 +53,14 @@ color: #333; } } + +.ui-sortable-handle { + cursor: move; + cursor: -webkit-grab; + cursor: -moz-grab; + + &:active { + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + } +} diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss index 2cd30491bf5de471ee1c144a0f693bb98d7840e6..b6f21fd8c91a20904798eee5dadb77d1817c116c 100644 --- a/app/assets/stylesheets/framework/pagination.scss +++ b/app/assets/stylesheets/framework/pagination.scss @@ -1,35 +1,11 @@ .gl-pagination { + text-align: center; border-top: 1px solid $border-color; - background-color: $background-color; - margin: -$gl-padding; + margin: 0; margin-top: 0; .pagination { padding: 0; - margin: 0; - display: block; - - li.first, - li.last, - li.next, - li.prev { - > a { - color: $link-color; - - &:hover { - color: #fff; - } - } - } - - li > a, - li > span { - border: none; - margin: 0; - @include border-radius(0 !important); - padding: 13px 19px; - border-right: 1px solid $border-color; - } } } diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index cd0621cdbf308decc205e393aa5a5db4d3495e69..798cd224ad0359c733367280c4f09c96bb402542 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -66,20 +66,20 @@ $legend-color: $text-color; //## $pagination-color: $gl-gray; -$pagination-bg: $background-color; -$pagination-border: transparent; +$pagination-bg: #fff; +$pagination-border: $border-color; -$pagination-hover-color: #fff; -$pagination-hover-bg: $brand-info; -$pagination-hover-border: transparent; +$pagination-hover-color: $gl-gray; +$pagination-hover-bg: $hover; +$pagination-hover-border: $border-color; -$pagination-active-color: #fff; -$pagination-active-bg: $brand-info; -$pagination-active-border: transparent; +$pagination-active-color: $blue-dark; +$pagination-active-bg: #fff; +$pagination-active-border: $border-color; -$pagination-disabled-color: #fff; -$pagination-disabled-bg: lighten($brand-info, 15%); -$pagination-disabled-border: transparent; +$pagination-disabled-color: #cdcdcd; +$pagination-disabled-bg: $background-color; +$pagination-disabled-border: $border-color; //== Form states and alerts diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 6a2b25ddc67905a29767100b831a84fe5a456be2..c7f7a5aaf070f1e7a64ac8af6953b7f193e03412 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -1,20 +1,40 @@ /* https://github.com/MozMorris/tomorrow-pygments */ .code.dark { + // Line numbers + .line-numbers, .diff-line-num { + background-color: #1d1f21; + } - background-color: #1d1f21 !important; - color: #c5c8c6 !important; - - pre.highlight, - .line-numbers, - .line-numbers a { - background-color: #1d1f21 !important; - color: #c5c8c6 !important; + .diff-line-num, .diff-line-num a { + color: rgba(255, 255, 255, 0.3); } + // Code itself pre.code { border-left: 1px solid #666; } + &, pre.code, .line_holder .line_content { + background-color: #1d1f21; + color: #c5c8c6; + } + + // Diff line + .line_holder { + .diff-line-num.new, .line_content.new { + @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.3), #808080); + } + + .diff-line-num.old, .line_content.old { + @include diff_background(rgba(255, 51, 51, 0.2), rgba(255, 51, 51, 0.3), #808080); + } + + .line_content.match { + color: rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.1); + } + } + // highlight line via anchor pre .hll { background-color: #557 !important; diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 8560c3c490fc1e3070148bb05828d97bbfee3ced..516d8aef96032c759b0d16c729b8bf60d8bad175 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -1,20 +1,40 @@ /* https://github.com/richleland/pygments-css/blob/master/monokai.css */ .code.monokai { + // Line numbers + .line-numbers, .diff-line-num { + background-color: #272822; + } - background-color: #272822 !important; - color: #f8f8f2 !important; - - pre.highlight, - .line-numbers, - .line-numbers a { - background-color :#272822 !important; - color: #f8f8f2 !important; + .diff-line-num, .diff-line-num a { + color: rgba(255, 255, 255, 0.3); } + // Code itself pre.code { border-left: 1px solid #555; } + &, pre.code, .line_holder .line_content { + background-color: #272822; + color: #f8f8f2; + } + + // Diff line + .line_holder { + .diff-line-num.new, .line_content.new { + @include diff_background(rgba(166, 226, 46, 0.2), rgba(166, 226, 46, 0.3), #808080); + } + + .diff-line-num.old, .line_content.old { + @include diff_background(rgba(254, 147, 140, 0.2), rgba(254, 147, 140, 0.3), #808080); + } + + .line_content.match { + color: rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.1); + } + } + // highlight line via anchor pre .hll { background-color: #49483e !important; diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index 7d489a9666b8b0828a5b7f4bf7670b0d6cdb23a4..ae7ecc65c39c630bff622a44e3ba8829f847910b 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -1,20 +1,40 @@ /* https://gist.github.com/qguv/7936275 */ .code.solarized-dark { + // Line numbers + .line-numbers, .diff-line-num { + background-color: #002b36; + } - background-color: #002b36 !important; - color: #93a1a1 !important; - - pre.highlight, - .line-numbers, - .line-numbers a { - background-color: #002b36 !important; - color: #93a1a1 !important; + .diff-line-num, .diff-line-num a { + color: rgba(255, 255, 255, 0.3); } + // Code itself pre.code { border-left: 1px solid #113b46; } + &, pre.code, .line_holder .line_content { + background-color: #002b36; + color: #93a1a1; + } + + // Diff line + .line_holder { + .diff-line-num.new, .line_content.new { + @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.3), #808080); + } + + .diff-line-num.old, .line_content.old { + @include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.3), #808080); + } + + .line_content.match { + color: rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.1); + } + } + // highlight line via anchor pre .hll { background-color: #174652 !important; diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 200ed346446e1dc110c09563a2a872914323f564..1c1385721458beb4f1081831cd2108e8a7d1a800 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -1,20 +1,40 @@ /* https://gist.github.com/qguv/7936275 */ .code.solarized-light { + // Line numbers + .line-numbers, .diff-line-num { + background-color: #fdf6e3; + } - background-color: #fdf6e3 !important; - color: #586e75 !important; - - pre.highlight, - .line-numbers, - .line-numbers a { - background-color: #fdf6e3 !important; - color: #586e75 !important; + .diff-line-num, .diff-line-num a { + color: rgba(0, 0, 0, 0.3); } + // Code itself pre.code { border-left: 1px solid #c5d0d4; } + &, pre.code, .line_holder .line_content { + background-color: #fdf6e3; + color: #586e75; + } + + // Diff line + .line_holder { + .diff-line-num.new, .line_content.new { + @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.3), #FAF3DD); + } + + .diff-line-num.old, .line_content.old { + @include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.3), #FAF3DD); + } + + .line_content.match { + color: rgba(0, 0, 0, 0.3); + background: rgba(255, 255, 255, 0.4); + } + } + // highlight line via anchor pre .hll { background-color: #ddd8c5 !important; diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index e2626da78712858274a8fb233595f770ce765254..8a932e6540e727a16ab9581f037652c68c9ba055 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,20 +1,60 @@ /* https://github.com/aahan/pygments-github-style */ .code.white { + // Line numbers + .line-numbers, .diff-line-num { + background-color: $background-color; + } - background-color: #f8fafc !important; - color: #5b6169 !important; - - pre.highlight, - .line-numbers, - .line-numbers a { - background-color: $background-color !important; - color: $gl-gray !important; + .diff-line-num, .diff-line-num a { + color: rgba(0, 0, 0, 0.3); } + // Code itself pre.code { border-left: 1px solid $border-color; - background-color: #fff !important; - color: #333 !important; + } + + &, pre.code, .line_holder .line_content { + background-color: #fff; + color: #333; + } + + // Diff line + .line_holder { + .diff-line-num { + &.old { + background: #ffdddd; + border-color: #f1c0c0; + } + + &.new { + background: #dbffdb; + border-color: #c1e9c1; + } + } + + .line_content { + &.old { + background: #ffecec; + + span.idiff { + background-color: #f8cbcb; + } + } + + &.new { + background: #eaffea; + + span.idiff { + background-color: #a6f3a6; + } + } + + &.match { + color: rgba(0, 0, 0, 0.3); + background: #fafafa; + } + } } // highlight line via anchor diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 04e9a58e1cf4c919d15a9ede1b5d8d660ec9980e..5215df04a6e2094c9bfcd3a0c9c1f3a31688d4df 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -32,16 +32,6 @@ background: #FFF; color: #333; - .old { - span.idiff { - background-color: #f8cbcb; - } - } - .new { - span.idiff { - background-color: #a6f3a6; - } - } .unfold { cursor: pointer; } @@ -76,7 +66,7 @@ } tr.line_holder.parallel { - .old_line, .new_line, .diff_line { + .old_line, .new_line { min-width: 50px; } @@ -85,7 +75,7 @@ } } - .old_line, .new_line, .diff_line { + .old_line, .new_line { margin: 0px; padding: 0px; border: none; @@ -107,43 +97,12 @@ text-decoration: underline; } } - &.new { - background: #CFD; - } - &.old { - background: #FDD; - } - } - .diff_line { - padding: 0; - } - .line_holder { - &.old .old_line, - &.old .new_line { - background: #ffdddd; - border-color: #f1c0c0; - } - &.new .old_line, - &.new .new_line { - background: #dbffdb; - border-color: #c1e9c1; - } } .line_content { display: block; margin: 0px; padding: 0px 0.5em; border: none; - &.new { - background: #eaffea; - } - &.old { - background: #ffecec; - } - &.matched { - color: $border-color; - background: #fafafa; - } &.parallel { display: table-cell; } @@ -393,3 +352,15 @@ right: 15px; } } + +@mixin diff_background($background, $idiff, $border) { + background: $background; + + &.line_content span.idiff { + background: $idiff; + } + + &.diff-line-num { + border-color: $border; + } +} diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 75f2ae80a92f49e07998c888a282e6acc2a23d05..f033ff15f881194de18202f67a7d5024d4aac43e 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -201,3 +201,39 @@ .mr-source-target { line-height: 31px; } + +.disabled-comment-area { + padding: 16px 0; + + .disabled-profile { + width: 40px; + height: 40px; + background: $border-gray-dark; + border-radius: 20px; + display: inline-block; + margin-right: 10px; + } + + .disabled-comment { + background: $gray-light; + display: inline-block; + vertical-align: top; + height: 200px; + border-radius: 4px; + border: 1px solid $border-gray-normal; + padding-top: 90px; + text-align: center; + right: 20px; + position: absolute; + left: 70px; + margin-bottom: 20px; + + span { + color: #B2B2B2; + + a { + color: $md-link-color; + } + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 2c9a42f9892eee20c5b23190b7886d354c301837..32ba1676333c217146028b70d6d7f46952cea275 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -10,18 +10,6 @@ margin: 10px $gl-padding; } .diff-file .diff-content { - tr.line_holder:hover { - &> td.line_content { - background: $hover !important; - border-color: darken($hover, 10%) !important; - } - &> td.new_line, - &> td.old_line { - background: darken($hover, 4%) !important; - border-color: darken($hover, 10%) !important; - } - } - tr.line_holder:hover > td .line_note_link { opacity: 1.0; filter: alpha(opacity=100); diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 72b0ed29a698b1d2e95b79c8c654f61784a2165f..f6343e6bb1ef97179f906ffe83e679492f440ea6 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -242,11 +242,8 @@ ul.notes { // "show" the icon also if we just hover somewhere over the line &:hover > td { - background: $hover !important; - .add-diff-note { @include show-add-diff-note; } } } - diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index bdcf18975220f3430603ee7c2f3458fd735b20bb..3aaa96da609172170cb7dc5907cf751e77d57cf1 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -3,10 +3,6 @@ border-bottom: 1px solid #DDD; padding-bottom: 15px; margin-bottom: 15px; - - .term { - height: 22px; - } } } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5d047f09f641bfd2f253e6dd5b169baad648ff02..540cc30bed583ce152fc13f473cdfa81ac95be0c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -25,6 +25,7 @@ class ApplicationController < ActionController::Base helper_method :abilities, :can?, :current_application_settings helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled? + helper_method :repository rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 62127a090817e01355ec314399db321c0d378a56..b9eb0a22f881532f4bbc61a650b4f52ed72a7f72 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -97,7 +97,7 @@ module CreatesCommit # Merge request from fork to this project @mr_source_project = @tree_edit_project @mr_target_project = @project - @mr_target_branch = @mr_target_project.repository.root_ref + @mr_target_branch = @ref end end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index c56a3497bb2d096cb00231fa979148c0c22c8037..bb72232edd715b1b917e4c40b2ba65198c3b9d95 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -52,7 +52,9 @@ class Projects::BlobController < Projects::ApplicationController def preview @content = params[:content] diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true) - @diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/)) + diff_lines = diffy.diff.scan(/.*\n/)[2..-1] + diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines) + @diff_lines = Gitlab::Diff::Highlight.new(diff_lines).highlight render layout: false end @@ -65,8 +67,9 @@ class Projects::BlobController < Projects::ApplicationController end def diff - @form = UnfoldForm.new(params) - @lines = @blob.data.lines[@form.since - 1..@form.to - 1] + @form = UnfoldForm.new(params) + @lines = Gitlab::Highlight.highlight_lines(repository, @ref, @path) + @lines = @lines[@form.since - 1..@form.to - 1] if @form.bottom? @match_line = '' diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 870f6795219a442b86d7cf702cb84e84f847154f..f5a169e5aa984847cc9435914d7745bdc5a88079 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -72,6 +72,7 @@ class Projects::CommitController < Projects::ApplicationController @diffs = commit.diffs end + @diff_refs = [commit.parent || commit, commit] @notes_count = commit.notes.count @statuses = ci_commit.statuses if ci_commit diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 5200d609cc98c458f19c438fa7c7630456e59573..f8ec76cd4e5763665ef9dfa168080b7d81f467c5 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -21,7 +21,8 @@ class Projects::CompareController < Projects::ApplicationController @commits = Commit.decorate(compare_result.commits, @project) @diffs = compare_result.diffs @commit = @project.commit(head_ref) - @first_commit = @project.commit(base_ref) + @base_commit = @project.commit(base_ref) + @diff_refs = [@base_commit, @commit] @line_notes = [] end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a6284a24223758dae533d730520f039beefb84f0..ed3050d59aafa73943aa6bfb50ae945e59503241 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -58,7 +58,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController def diffs @commit = @merge_request.last_commit - @first_commit = @merge_request.first_commit + @base_commit = @merge_request.diff_base_commit + + # MRs created before 8.4 don't have a diff_base_commit, + # but we need it for the "View file @ ..." link by deleted files + @base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit @comments_allowed = @reply_allowed = true @comments_target = { @@ -102,7 +106,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @source_project = merge_request.source_project @commits = @merge_request.compare_commits.reverse @commit = @merge_request.last_commit - @first_commit = @merge_request.first_commit + @base_commit = @merge_request.diff_base_commit @diffs = @merge_request.compare_diffs @ci_commit = @merge_request.ci_commit diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 6f1e186d4084d4eb4729a74b989785c48115ad6a..4a2599dda37fd2d6a65f0496e098c43450669dee 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -11,11 +11,9 @@ class Projects::NotesController < Projects::ApplicationController notes_json = { notes: [], last_fetched_at: current_fetched_at } @notes.each do |note| - notes_json[:notes] << { - id: note.id, - html: note_to_html(note), - valid: note.valid? - } + next if note.cross_reference_not_visible_for?(current_user) + + notes_json[:notes] << note_json(note) end render json: notes_json @@ -25,7 +23,7 @@ class Projects::NotesController < Projects::ApplicationController @note = Notes::CreateService.new(project, current_user, note_params).execute respond_to do |format| - format.json { render_note_json(@note) } + format.json { render json: note_json(@note) } format.html { redirect_back_or_default } end end @@ -34,7 +32,7 @@ class Projects::NotesController < Projects::ApplicationController @note = Notes::UpdateService.new(project, current_user, note_params).execute(note) respond_to do |format| - format.json { render_note_json(@note) } + format.json { render json: note_json(@note) } format.html { redirect_back_or_default } end end @@ -99,6 +97,8 @@ class Projects::NotesController < Projects::ApplicationController end def note_to_discussion_html(note) + return unless note.for_diff_line? + if params[:view] == 'parallel' template = "projects/notes/_diff_notes_with_reply_parallel" locals = @@ -131,9 +131,9 @@ class Projects::NotesController < Projects::ApplicationController ) end - def render_note_json(note) + def note_json(note) if note.valid? - render json: { + { valid: true, id: note.id, discussion_id: note.discussion_id, @@ -144,7 +144,7 @@ class Projects::NotesController < Projects::ApplicationController discussion_with_diff_html: note_to_discussion_with_diff_html(note) } else - render json: { + { valid: false, award: note.is_award, errors: note.errors @@ -163,8 +163,6 @@ class Projects::NotesController < Projects::ApplicationController ) end - private - def find_current_user_notes @notes = NotesFinder.new.execute(project, current_user, params) end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index d31d4cde08f4dcb2579d2fd582c50111401e9bca..8b689b29a410edb29d47be48bb9424942a5f5779 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -1,21 +1,10 @@ module BlobHelper - def highlight(blob_name, blob_content, nowrap: false, continue: false) - @formatter ||= Rouge::Formatters::HTMLGitlab.new( - nowrap: nowrap, - cssclass: 'code highlight', - lineanchors: true, - lineanchorsid: 'LC' - ) - - begin - @lexer ||= Rouge::Lexer.guess(filename: blob_name, source: blob_content).new - result = @formatter.format(@lexer.lex(blob_content, continue: continue)).html_safe - rescue - @lexer = Rouge::Lexers::PlainText - result = @formatter.format(@lexer.lex(blob_content)).html_safe - end + def highlighter(blob_name, blob_content, nowrap: false) + Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap) + end - result + def highlight(blob_name, blob_content, nowrap: false) + Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap) end def no_highlight_files @@ -37,10 +26,10 @@ module BlobHelper tree_join(ref, path), link_opts) - if !on_top_of_branch? + if !on_top_of_branch?(project, ref) button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } - elsif can_edit_blob?(blob) - link_to "Edit", edit_path, class: 'btn btn-small' + elsif can_edit_blob?(blob, project, ref) + link_to "Edit", edit_path, class: 'btn' elsif can?(current_user, :fork_project, project) continue_params = { to: edit_path, @@ -50,7 +39,7 @@ module BlobHelper fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params) - link_to "Edit", fork_path, class: 'btn btn-small', method: :post + link_to "Edit", fork_path, class: 'btn', method: :post end end @@ -61,11 +50,11 @@ module BlobHelper return unless blob - if !on_top_of_branch? + if !on_top_of_branch?(project, ref) button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' } elsif blob.lfs_pointer? button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' } - elsif can_edit_blob?(blob) + elsif can_edit_blob?(blob, project, ref) button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal' elsif can?(current_user, :fork_project, project) continue_params = { diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 590d20ac7b3495f37611935c015db301f2fdbce4..d26f007c8e65e36d356a22999ea28c00d6ac44b4 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -166,7 +166,7 @@ module CommitsHelper link_to( namespace_project_blob_path(project.namespace, project, tree_join(commit_sha, diff.new_path)), - class: 'btn btn-small view-file js-view-file' + class: 'btn view-file js-view-file' ) do raw('View file @') + content_tag(:span, commit_sha[0..6], class: 'commit-short-id') diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 24134310fc5d084b4cfa15f1e1c6009b9c51f476..62971d1e14b531e2984e2e83e902aeda3ef8f3ab 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -19,13 +19,13 @@ module DiffHelper end end - def safe_diff_files(diffs) + def safe_diff_files(diffs, diff_refs) lines = 0 safe_files = [] diffs.first(allowed_diff_size).each do |diff| lines += diff.diff.lines.count break if lines > allowed_diff_lines - safe_files << Gitlab::Diff::File.new(diff) + safe_files << Gitlab::Diff::File.new(diff, diff_refs) end safe_files end @@ -43,64 +43,6 @@ module DiffHelper Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) end - def parallel_diff(diff_file, index) - lines = [] - skip_next = false - - # Building array of lines - # - # [ - # left_type, left_line_number, left_line_content, left_line_code, - # right_line_type, right_line_number, right_line_content, right_line_code - # ] - # - diff_file.diff_lines.each do |line| - - full_line = line.text - type = line.type - line_code = generate_line_code(diff_file.file_path, line) - line_new = line.new_pos - line_old = line.old_pos - - next_line = diff_file.next_line(line.index) - - if next_line - next_line_code = generate_line_code(diff_file.file_path, next_line) - next_type = next_line.type - next_line = next_line.text - end - - if type == 'match' || type.nil? - # line in the right panel is the same as in the left one - line = [type, line_old, full_line, line_code, type, line_new, full_line, line_code] - lines.push(line) - elsif type == 'old' - if next_type == 'new' - # Left side has text removed, right side has text added - line = [type, line_old, full_line, line_code, next_type, line_new, next_line, next_line_code] - lines.push(line) - skip_next = true - elsif next_type == 'old' || next_type.nil? - # Left side has text removed, right side doesn't have any change - # No next line code, no new line number, no new line text - line = [type, line_old, full_line, line_code, next_type, nil, " ", nil] - lines.push(line) - end - elsif type == 'new' - if skip_next - # Change has been already included in previous line so no need to do it again - skip_next = false - next - else - # Change is only on the right side, left side has no change - line = [nil, nil, " ", line_code, type, line_new, full_line, line_code] - lines.push(line) - end - end - end - lines - end - def unfold_bottom_class(bottom) (bottom) ? 'js-unfold-bottom' : '' end @@ -111,9 +53,9 @@ module DiffHelper def diff_line_content(line) if line.blank? - " " + " ".html_safe else - line + line.html_safe end end @@ -160,8 +102,7 @@ module DiffHelper def commit_for_diff(diff) if diff.deleted_file - first_commit = @first_commit || @commit - first_commit.parent || @first_commit + @base_commit || @commit.parent || @commit else @commit end diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index 906cb12cd48f911c0e1fde91be1a577315c682e7..bc36434f5492c7250e72b82cc72175d225a97dba 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -17,4 +17,79 @@ module SnippetsHelper snippet_path(snippet) end end + + # Get an array of line numbers surrounding a matching + # line, bounded by min/max. + # + # @returns Array of line numbers + def bounded_line_numbers(line, min, max, surrounding_lines) + lower = line - surrounding_lines > min ? line - surrounding_lines : min + upper = line + surrounding_lines < max ? line + surrounding_lines : max + (lower..upper).to_a + end + + # Returns a sorted set of lines to be included in a snippet preview. + # This ensures matching adjacent lines do not display duplicated + # surrounding code. + # + # @returns Array, unique and sorted. + def matching_lines(lined_content, surrounding_lines) + used_lines = [] + lined_content.each_with_index do |line, line_number| + used_lines.concat bounded_line_numbers( + line_number, + 0, + lined_content.size, + surrounding_lines + ) if line.include?(query) + end + + used_lines.uniq.sort + end + + # 'Chunkify' entire snippet. Splits the snippet data into matching lines + + # surrounding_lines() worth of unmatching lines. + # + # @returns a hash with {snippet_object, snippet_chunks:{data,start_line}} + def chunk_snippet(snippet, surrounding_lines = 3) + lined_content = snippet.content.split("\n") + used_lines = matching_lines(lined_content, surrounding_lines) + + snippet_chunk = [] + snippet_chunks = [] + snippet_start_line = 0 + last_line = -1 + + # Go through each used line, and add consecutive lines as a single chunk + # to the snippet chunk array. + used_lines.each do |line_number| + if last_line < 0 + # Start a new chunk. + snippet_start_line = line_number + snippet_chunk << lined_content[line_number] + elsif last_line == line_number - 1 + # Consecutive line, continue chunk. + snippet_chunk << lined_content[line_number] + else + # Non-consecutive line, add chunk to chunk array. + snippet_chunks << { + data: snippet_chunk.join("\n"), + start_line: snippet_start_line + 1 + } + + # Start a new chunk. + snippet_chunk = [lined_content[line_number]] + snippet_start_line = line_number + end + last_line = line_number + end + # Add final chunk to chunk array + snippet_chunks << { + data: snippet_chunk.join("\n"), + start_line: snippet_start_line + 1 + } + + # Return snippet with chunk array + { snippet_object: snippet, snippet_chunks: snippet_chunks } + end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 16a5b03f5915c7c28e03f2fcd252257dd97064b3..623edd8bc573921d3e5e19d83cfde0b3b787d086 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -346,17 +346,17 @@ module Ci end def artifacts_browse_url - if artifacts_browser_supported? + if artifacts_metadata? browse_namespace_project_build_artifacts_path(project.namespace, project, self) end end - def artifacts_browser_supported? + def artifacts_metadata? artifacts? && artifacts_metadata.exists? end - def artifacts_metadata_entry(path) - Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path).to_entry + def artifacts_metadata_entry(path, **options) + Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry end private diff --git a/app/models/issue.rb b/app/models/issue.rb index 7beba9846089b51f6d6263fbc29af6f6a35e97aa..5f58c0508fd0279966120d0e5bc7096c850dd4e9 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -38,6 +38,7 @@ class Issue < ActiveRecord::Base scope :cared, ->(user) { where(assignee_id: user) } scope :open_for, ->(user) { opened.assigned_to(user) } + scope :in_projects, ->(project_ids) { where(project_id: project_ids) } state_machine :state, initial: :opened do event :close do diff --git a/app/models/member.rb b/app/models/member.rb index 28aee2e379963224b4459c4b5251d10464528d63..34efcd0088d006722958d0ff2c0003bcb4935cf2 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -91,7 +91,7 @@ class Member < ActiveRecord::Base member.invite_email = user end - if can_update_member?(current_user, member) + if can_update_member?(current_user, member) || project_creator?(member, access_level) member.created_by ||= current_user member.access_level = access_level @@ -107,6 +107,11 @@ class Member < ActiveRecord::Base current_user.can?(:update_group_member, member) || current_user.can?(:update_project_member, member) end + + def project_creator?(member, access_level) + member.new_record? && member.owner? && + access_level.to_i == ProjectMember::MASTER + end end def invite? diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 1b0c76917aa377d47abb47c2be4716fe5c8a9976..560d1690e1432de9698378c9515eddccc60d81a9 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -84,7 +84,7 @@ class ProjectMember < Member def truncate_teams(project_ids) ProjectMember.transaction do members = ProjectMember.where(source_id: project_ids) - + members.each do |member| member.destroy end @@ -133,13 +133,13 @@ class ProjectMember < Member event_service.join_project(self.project, self.user) notification_service.new_project_member(self) end - + super end def post_update_hook if access_level_changed? - notification_service.update_project_member(self) + notification_service.update_project_member(self) end super diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index c63d0c016537099a8ceaed35a26aa9bd64518cc0..41dd248d80a2ad8def8d55336d5145f0415a85f8 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -180,6 +180,14 @@ class MergeRequest < ActiveRecord::Base merge_request_diff ? merge_request_diff.first_commit : compare_commits.first end + def diff_base_commit + if merge_request_diff + merge_request_diff.base_commit + else + self.target_project.commit(self.target_branch) + end + end + def last_commit_short_sha last_commit.short_id end @@ -254,7 +262,7 @@ class MergeRequest < ActiveRecord::Base end def mergeable? - return false unless open? && !work_in_progress? + return false unless open? && !work_in_progress? && !broken? check_if_can_be_merged @@ -477,8 +485,7 @@ class MergeRequest < ActiveRecord::Base end def target_sha - @target_sha ||= target_project. - repository.commit(target_branch).sha + @target_sha ||= target_project.repository.commit(target_branch).sha end def source_sha @@ -517,4 +524,10 @@ class MergeRequest < ActiveRecord::Base def ci_commit @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project end + + def diff_refs + return nil unless diff_base_commit + + [diff_base_commit, last_commit] + end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index c499a4b5b4c8006f834fcf3ffcaee513d89120ec..ba0194cd0a69d7733173b0bea9eb93831ff65469 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -73,6 +73,12 @@ class MergeRequestDiff < ActiveRecord::Base commits.last end + def base_commit + return nil unless self.base_commit_sha + + merge_request.target_project.commit(self.base_commit_sha) + end + def last_commit_short_sha @last_commit_short_sha ||= last_commit.short_id end @@ -156,6 +162,9 @@ class MergeRequestDiff < ActiveRecord::Base end self.st_diffs = new_diffs + + self.base_commit_sha = merge_request.target_project.commit(target_branch).try(:sha) + self.save end diff --git a/app/models/note.rb b/app/models/note.rb index 15f48110ad2471a922c050e88668a235a0bc3f30..55255d22c2f4f3250f931b127c80cd28ea3a7a2c 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -244,7 +244,7 @@ class Note < ActiveRecord::Base prev_match_line = nil prev_lines = [] - diff_lines.each do |line| + highlighted_diff_lines.each do |line| if line.type == "match" prev_lines.clear prev_match_line = line @@ -261,7 +261,11 @@ class Note < ActiveRecord::Base end def diff_lines - @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a) + @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines) + end + + def highlighted_diff_lines + Gitlab::Diff::Highlight.new(diff_lines).highlight end def discussion_id diff --git a/app/models/project.rb b/app/models/project.rb index 5579710a47666a98a97e9b49f539b66157fa6088..4bd51449c25051ca7dba47ad1186473d2266486a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -904,4 +904,8 @@ class Project < ActiveRecord::Base def runners_token ensure_runners_token! end + + def wiki + @wiki ||= ProjectWiki.new(self, self.owner) + end end diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 8ce47495971b97d25225e5a1a42c4d5a3f92f500..c847eba8d1c9b340fef154c1c4391f7f81f8d7bf 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -12,6 +12,7 @@ class ProjectWiki # Returns a string describing what went wrong after # an operation fails. attr_reader :error_message + attr_reader :project def initialize(project, user = nil) @project = project diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index c94d7ab710f4feff827152790acc82502d3e1ba0..a6820183bee9ed83a3d94a3b9cb20933f9a00ec8 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -95,7 +95,7 @@ module Projects system_hook_service.execute_hooks_for(@project, :create) unless @project.group - @project.team << [current_user, :master] + @project.team << [current_user, :master, current_user] end @project.import_start if @project.import? diff --git a/app/views/kaminari/gitlab/_next_page.html.haml b/app/views/kaminari/gitlab/_next_page.html.haml index 00c5f0b6f4e3fc9a71c81d2a674fcaac213c856d..c805914fc3fcc03033c27734eefa49c2173b711f 100644 --- a/app/views/kaminari/gitlab/_next_page.html.haml +++ b/app/views/kaminari/gitlab/_next_page.html.haml @@ -5,5 +5,9 @@ -# num_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%li.next - = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote +- if current_page.last? + %li{ class: "next disabled" } + %span= raw(t 'views.pagination.next') +- else + %li{ class: "next" } + = link_to raw(t 'views.pagination.next'), url, rel: 'next', remote: remote diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml index 2f645186921885198c412560842cf6eeffa00844..a12c53bcfe713401ebcf6d1e026f5edd59cf6917 100644 --- a/app/views/kaminari/gitlab/_paginator.html.haml +++ b/app/views/kaminari/gitlab/_paginator.html.haml @@ -10,13 +10,13 @@ %ul.pagination.clearfix - unless current_page.first? = first_page_tag unless num_pages < 5 # As kaminari will always show the first 5 pages - = prev_page_tag + = prev_page_tag - each_page do |page| - if page.left_outer? || page.right_outer? || page.inside_window? = page_tag page - elsif !page.was_truncated? = gap_tag + = next_page_tag - unless current_page.last? - = next_page_tag = last_page_tag unless num_pages < 5 diff --git a/app/views/kaminari/gitlab/_prev_page.html.haml b/app/views/kaminari/gitlab/_prev_page.html.haml index f673abdb3ae8b6cc9859f1ede9d24a44e431c551..afb20455e0a3727f2fb70f7fed0b489a9af03f43 100644 --- a/app/views/kaminari/gitlab/_prev_page.html.haml +++ b/app/views/kaminari/gitlab/_prev_page.html.haml @@ -5,5 +5,9 @@ -# num_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%li{class: "prev" } - = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote +- if current_page.first? + %li{ class: "prev disabled" } + %span= raw(t 'views.pagination.previous') +- else + %li{ class: "prev" } + = link_to raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index d3c969cc035fe2f579f2dd30a3f2dd19931286cf..84034c8bf16e952e173235f1b991950014d7e97e 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -4,7 +4,7 @@ .top-block.gray-content-block.clearfix .pull-right = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), - class: 'btn btn-default' do + class: 'btn btn-default download' do = icon('download') Download artifacts archive @@ -20,12 +20,3 @@ - if @entry.empty? .center Empty - -:javascript - $('.tree-holder').on('click', 'tr[data-link] a', function(e) { - e.stopImmediatePropagation(); - }); - - $('.tree-holder').on('click', 'tr[data-link]', function(e) { - window.location = this.dataset.link; - }); diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index 8d9ec068a4358a38ba030fb9665033a67d9ea643..53dcac78a9f9e1c7da6177aee9bb7b0ba1dbf1ac 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -12,9 +12,10 @@ %small= number_to_human_size @blob.size .file-actions = render "projects/blob/actions" - .file-content.blame.highlight + .file-content.blame.code.js-syntax-highlight %table - current_line = 1 + - blame_highlighter = highlighter(@blob.name, @blob.data, nowrap: true) - @blame.each do |blame_group| %tr %td.blame-commit @@ -30,16 +31,15 @@ = commit_author_link(commit, avatar: false) authored #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} - %td.lines.blame-numbers - %pre - - line_count = blame_group[:lines].count - - (current_line...(current_line + line_count)).each do |i| - = i - \ - - current_line += line_count + %td.line-numbers + - line_count = blame_group[:lines].count + - (current_line...(current_line + line_count)).each do |i| + %a.diff-line-num= i + \ + - current_line += line_count %td.lines - %pre{class: 'code highlight white'} + %pre{class: 'code highlight'} %code - blame_group[:lines].each do |line| - :erb - <%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %> + :preserve + #{blame_highlighter.highlight(line)} diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index f3b01ff3288b1723eecaa8f266eae7111746514c..abcfca4cd11bf876978ca7e8023502c5534b39e5 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -10,8 +10,9 @@ %tr.line_holder %td.old_line.diff-line-num{data: {linenumber: line_old}} = link_to raw(line_old), "#" - %td.new_line= link_to raw(line_new) , "#" - %td.line_content.noteable_line= ' ' * @form.indent + line + %td.new_line.diff-line-num + = link_to raw(line_new) , "#" + %td.line_content.noteable_line==#{' ' * @form.indent}#{line} - if @form.unfold? && @form.bottom? && @form.to < @blob.loc %tr.line_holder{ id: @form.to } diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml index e7c3460ad7852ec49bfe84f0e0af009ec2634bd3..c5a269f334c6d62fdbafc3f4a735d41156c4d49a 100644 --- a/app/views/projects/blob/preview.html.haml +++ b/app/views/projects/blob/preview.html.haml @@ -14,12 +14,12 @@ - @diff_lines.each do |line| %tr.line_holder{ class: "#{line.type}" } - if line.type == "match" - %td.old_line= "..." - %td.new_line= "..." - %td.line_content.matched= line.text + %td.old_line.diff-line-num= "..." + %td.new_line.diff-line-num= "..." + %td.line_content.match= line.text - else - %td.old_line - %td.new_line - %td.line_content{class: "#{line.type}"}= raw diff_line_content(line.text) + %td.old_line.diff-line-num + %td.new_line.diff-line-num + %td.line_content{class: "#{line.type}"}= diff_line_content(line.text) - else .nothing-here-block No changes. diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 2be572d3b10edfe9f690066c7d6add3fca753f0a..ba1fdc6f0e7416ea88419de2784facb9baf96e73 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -96,7 +96,7 @@ .center .btn-group{ role: :group } = link_to "Download", @build.artifacts_download_url, class: 'btn btn-sm btn-primary' - - if @build.artifacts_browser_supported? + - if @build.artifacts_metadata? = link_to "Browse", @build.artifacts_browse_url, class: 'btn btn-sm btn-primary' .build-widget diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 02297158dec5964333e0e1e6ee09e5a18070fa41..05dbe5ebea41292ccdad88a99c81ce54931b7b47 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -9,5 +9,6 @@ = render "ci_menu" - else %div.block-connector -= render "projects/diffs/diffs", diffs: @diffs, project: @project += render "projects/diffs/diffs", diffs: @diffs, project: @project, + diff_refs: @diff_refs = render "projects/notes/notes_with_form" diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 51088a7dea89b0e97622dde7a1b0ba641ea8c0d8..da731f28bb608109b7b221601e9069a0c4606d39 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -9,7 +9,7 @@ - if @commits.present? .prepend-top-default = render "projects/commits/commit_list" - = render "projects/diffs/diffs", diffs: @diffs, project: @project + = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs - else .light-well.prepend-top-default .center diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index f67058ae0ba86e0b52de2fda81316ac276e37bb4..d668f483bcb3e40fec09247d8c07c4896949e275 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -1,7 +1,7 @@ - if diff_view == 'parallel' - fluid_layout true -- diff_files = safe_diff_files(diffs) +- diff_files = safe_diff_files(diffs, diff_refs) .content-block.oneline-block .inline-parallel-buttons diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 517f6aef7c5a89e356407058e781455467cb8640..fc0eaef22866729a7aa10ad0a1a768bcd176fd7c 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -1,39 +1,37 @@ -.diff-file{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)} - .diff-header{id: "file-path-#{hexdigest(diff_file.file_path)}"} +.diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)} + .file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"} - if diff_file.diff.submodule? %span = icon('archive fw') %strong = submodule_link(blob, @commit.id, project.repository) - else - %span - = blob_icon blob.mode, blob.name - = link_to "#diff-#{i}" do - %strong - = diff_file.new_path + = blob_icon blob.mode, blob.name + = link_to "#diff-#{i}" do + %strong + = diff_file.new_path - - if diff_file.deleted_file - deleted - - elsif diff_file.renamed_file - renamed from - %strong - = diff_file.old_path + - if diff_file.deleted_file + deleted + - elsif diff_file.renamed_file + renamed from + %strong + = diff_file.old_path - - if diff_file.mode_changed? - %small - = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" + - if diff_file.mode_changed? + %small + = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" - .diff-controls + .file-actions.hidden-xs - if blob_text_viewable?(blob) - = link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do - %i.fa.fa-comments - + = link_to '#', class: 'js-toggle-diff-comments btn active has_tooltip', title: "Toggle comments for this file" do + = icon('comments') + \ - if editable_diff?(diff_file) = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, from_merge_request_id: @merge_request.id) - = view_file_btn(diff_commit.id, diff_file, project) diff --git a/app/views/projects/diffs/_match_line.html.haml b/app/views/projects/diffs/_match_line.html.haml index d1f897b99f78680591956eb00f967bf21ef9a90b..d6dddd97879ce85f9f53667eaa91e7055ff1a0f4 100644 --- a/app/views/projects/diffs/_match_line.html.haml +++ b/app/views/projects/diffs/_match_line.html.haml @@ -4,4 +4,4 @@ %td.new_line.diff-line-num{data: {linenumber: line_new}, class: [unfold_bottom_class(bottom), unfold_class(!new_file)]} \... -%td.line_content.matched= line +%td.line_content.match= line diff --git a/app/views/projects/diffs/_match_line_parallel.html.haml b/app/views/projects/diffs/_match_line_parallel.html.haml index 815df16aa4ab92ed646ae99df498d78fed75a5b0..0cd888876e02bc4ea7cf0f76fb5006353072af06 100644 --- a/app/views/projects/diffs/_match_line_parallel.html.haml +++ b/app/views/projects/diffs/_match_line_parallel.html.haml @@ -1,4 +1,4 @@ -%td.old_line - %td.line_content.parallel.matched= line -%td.new_line - %td.line_content.parallel.matched= line +%td.old_line.diff-line-num +%td.line_content.parallel.match= line +%td.new_line.diff-line-num +%td.line_content.parallel.match= line diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index 37fd1b1ec8a34d4ce6a276f4e38e681af01164a0..d7c490687452048974bb951e4f9232676644a70e 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -1,42 +1,40 @@ / Side-by-side diff view -%div.text-file.diff-wrap-lines +%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight %table - - parallel_diff(diff_file, index).each do |line| - - type_left = line[0] - - line_number_left = line[1] - - line_content_left = line[2] - - line_code_left = line[3] - - type_right = line[4] - - line_number_right = line[5] - - line_content_right = line[6] - - line_code_right = line[7] - + - diff_file.parallel_diff_lines.each do |line| + - left = line[:left] + - right = line[:right] %tr.line_holder.parallel - - if type_left == 'match' - = render "projects/diffs/match_line_parallel", { line: line_content_left, - line_old: line_number_left, line_new: line_number_right } - - elsif type_left == 'old' || type_left.nil? - %td.old_line{id: line_code_left, class: "#{type_left}"} - = link_to raw(line_number_left), "##{line_code_left}", id: line_code_left + - if left[:type] == 'match' + = render "projects/diffs/match_line_parallel", { line: left[:text], + line_old: left[:number], line_new: right[:number] } + - elsif left[:type] == 'nonewline' + %td.old_line.diff-line-num + %td.line_content.parallel.match= left[:text] + %td.new_line.diff-line-num + %td.line_content.parallel.match= left[:text] + - else + %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]}"} + = link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code] - if @comments_allowed && can?(current_user, :create_note, @project) - = link_to_new_diff_note(line_code_left, 'old') - %td.line_content{class: "parallel noteable_line #{type_left} #{line_code_left}", "line_code" => line_code_left }= raw line_content_left + = link_to_new_diff_note(left[:line_code], 'old') + %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text]) - - if type_right == 'new' + - if right[:type] == 'new' - new_line_class = 'new' - - new_line_code = line_code_right + - new_line_code = right[:line_code] - else - new_line_class = nil - - new_line_code = line_code_left + - new_line_code = left[:line_code] - %td.new_line{id: new_line_code, class: "#{new_line_class}", data: { linenumber: line_number_right }} - = link_to raw(line_number_right), "##{new_line_code}", id: new_line_code + %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class}", data: { linenumber: right[:number] }} + = link_to raw(right[:number]), "##{new_line_code}", id: new_line_code - if @comments_allowed && can?(current_user, :create_note, @project) - = link_to_new_diff_note(line_code_right, 'new') - %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", "line_code" => new_line_code}= raw line_content_right + = link_to_new_diff_note(right[:line_code], 'new') + %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", data: { line_code: new_line_code }}= diff_line_content(right[:text]) - if @reply_allowed - - comments_left, comments_right = organize_comments(type_left, type_right, line_code_left, line_code_right) + - comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code]) - if comments_left.present? || comments_right.present? = render "projects/notes/diff_notes_with_reply_parallel", notes_left: comments_left, notes_right: comments_right diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index 977ca423f75323cd1b13ac18c5307a7e64cca777..5e835b10e1fe5b70500581b71fda115bc2b3cff6 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -3,9 +3,11 @@ .suppressed-container %a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show. -%table.text-file{class: "#{'hide' if too_big}"} +%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' } + - last_line = 0 - - diff_file.diff_lines.each_with_index do |line, index| + - raw_diff_lines = diff_file.diff_lines + - diff_file.highlighted_diff_lines.each_with_index do |line, index| - type = line.type - last_line = line.new_pos - line_code = generate_line_code(diff_file.file_path, line) @@ -14,19 +16,23 @@ - if type == "match" = render "projects/diffs/match_line", {line: line.text, line_old: line_old, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file} + - elsif type == 'nonewline' + %td.old_line.diff-line-num + %td.new_line.diff-line-num + %td.line_content.match= line.text - else - %td.old_line + %td.old_line.diff-line-num{class: type} = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(line_code) - %td.new_line{data: {linenumber: line.new_pos}} - = link_to raw(type == "old" ? " " : line.new_pos) , "##{line_code}", id: line_code - %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text) + %td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}} + = link_to raw(type == "old" ? " " : line.new_pos), "##{line_code}", id: line_code + %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text) - if @reply_allowed - comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at) - unless comments.empty? - = render "projects/notes/diff_notes_with_reply", notes: comments, line: line.text + = render "projects/notes/diff_notes_with_reply", notes: comments, line: raw_diff_lines[index].text - if last_line > 0 = render "projects/diffs/match_line", {line: "", diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index e0e89b764d59eaf9d93adaf5c8a08625049636c5..f34f3c0573743d4cb746e3e51399c6d1054907bb 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -5,9 +5,4 @@ .nothing-here-block No issues to show - if @issues.present? - .issuable-filter-count - %span.pull-right - = number_with_delimiter(@issues.total_count) - issues for this filter - = paginate @issues, theme: "gitlab" diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml index 29d09d0a6521de10e026924c0ebbf8d0fee40507..5473fa191662af30e0ecc02be3652e041e453e4f 100644 --- a/app/views/projects/merge_requests/_merge_requests.html.haml +++ b/app/views/projects/merge_requests/_merge_requests.html.haml @@ -5,10 +5,5 @@ .nothing-here-block No merge requests to show - if @merge_requests.present? - .issuable-filter-count - %span.pull-right - = number_with_delimiter(@merge_requests.total_count) - merge requests for this filter - = paginate @merge_requests, theme: "gitlab" diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index dd2c59e112a63eec5a73017843e6ce6939c09ceb..4c5a9818e3e636ee976600f88561b8f200916766 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -38,7 +38,7 @@ = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane.active - if @diffs.present? - = render "projects/diffs/diffs", diffs: @diffs, project: @project + = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE .alert.alert-danger %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index d9cfc3d7ae943ba9df5f8afa7140a76e1463ec17..64cd484193e810d39f4774d45d18926f1eb341ae 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,5 +1,6 @@ - if @merge_request_diff.collected? - = render "projects/diffs/diffs", diffs: params[:w] == '1' ? @merge_request.diffs_no_whitespace : @merge_request.diffs, project: @merge_request.project + = render "projects/diffs/diffs", diffs: params[:w] == '1' ? @merge_request.diffs_no_whitespace : @merge_request.diffs, + project: @merge_request.project, diff_refs: @merge_request.diff_refs - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} - else diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml index c731baf0a651e225be51be50b74787999a2eb3f1..11f9859a90f4e7c4004d9cd77646784ab9f49d48 100644 --- a/app/views/projects/notes/_diff_notes_with_reply.html.haml +++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml @@ -7,7 +7,7 @@ %i.fa.fa-comment = notes.count %td.notes_content - %ul.notes{ rel: note.discussion_id } + %ul.notes{ data: { discussion_id: note.discussion_id } } = render notes .discussion-reply-holder = link_to_reply_diff(note) diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml index c6726cbafa3eb5941bed705e0873c83386e312e8..bb761ed2f946b0dd21438077b86253a1ab8ddbaa 100644 --- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml +++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml @@ -8,7 +8,7 @@ %i.fa.fa-comment = notes_left.count %td.notes_content.parallel.old - %ul.notes{ rel: note1.discussion_id } + %ul.notes{ data: { discussion_id: note1.discussion_id } } = render notes_left .discussion-reply-holder @@ -23,7 +23,7 @@ %i.fa.fa-comment = notes_right.count %td.notes_content.parallel.new - %ul.notes{ rel: note2.discussion_id } + %ul.notes{ data: { discussion_id: note2.discussion_id } } = render notes_right .discussion-reply-holder @@ -31,4 +31,3 @@ - else %td.notes_line.new= "" %td.notes_content.parallel.new= "" - diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 922535e5c4a67fbc22a94684d3c24ef45bdee9e6..e858c412836075137a5469299b6a577b70c2350e 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -1,4 +1,4 @@ -%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } } +%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] } .timeline-entry-inner .timeline-icon %a{href: user_path(note.author)} diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index eb378b4260367a15996999c525430642ae2660fa..910eb6cf66ec871baa89b080abc4116eedf6a899 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -5,6 +5,16 @@ .js-main-target-form - if can? current_user, :create_note, @project = render "projects/notes/form", view: diff_view +- else + .disabled-comment-area + .disabled-profile + .disabled-comment + %span + Please + = link_to "register",new_user_session_path + or + = link_to "login",new_user_session_path + to post a comment :javascript var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}") diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml index 6903fad4a0aa53ec20be59613334e6746a8561af..3da2f2060b8845de60243c7b01bb09c52315c948 100644 --- a/app/views/projects/notes/discussions/_commit.html.haml +++ b/app/views/projects/notes/discussions/_commit.html.haml @@ -20,8 +20,7 @@ = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note - else .panel.panel-default - .notes{ rel: discussion_notes.first.discussion_id } + .notes{ data: { discussion_id: discussion_notes.first.discussion_id } } = render discussion_notes .discussion-reply-holder = link_to_reply_diff(discussion_notes.first) - diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml index 0301445b5b28f02c18eeb456acde6e2dba3bdc96..92bbaf0961a18b35f0c6f8750f987dc58e98e63d 100644 --- a/app/views/projects/notes/discussions/_diff.html.haml +++ b/app/views/projects/notes/discussions/_diff.html.haml @@ -16,15 +16,15 @@ - line_code = generate_line_code(note.file_path, line) %tr.line_holder{ id: line_code, class: "#{type}" } - if type == "match" - %td.old_line= "..." - %td.new_line= "..." - %td.line_content.matched= line.text + %td.old_line.diff-line-num= "..." + %td.new_line.diff-line-num= "..." + %td.line_content.match= line.text - else - %td.old_line + %td.old_line.diff-line-num = raw(type == "new" ? " " : line.old_pos) - %td.new_line + %td.new_line.diff-line-num = raw(type == "old" ? " " : line.new_pos) - %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text) + %td.line_content{class: "noteable_line #{type} #{line_code}", line_code: line_code}= 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/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index 2efa616d6644e453a53415d6fcf8fbdac5eb4c33..faeb2b55c6f7a6d3791053f858b725c2a8f06983 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -6,7 +6,7 @@ - if merge_request.description.present? .description.term = preserve do - = search_md_sanitize(markdown(merge_request.description)) + = search_md_sanitize(markdown(merge_request.description, { project: merge_request.project })) %span.light #{merge_request.project.name_with_namespace} .pull-right diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index 9a4f9fb9485433c382bfdce23f6ea87be3572f78..dcd6119971736c27d651482c2f218b999c2af8c2 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -22,29 +22,27 @@ .file-content.code .nothing-here-block Empty file - else - .file-content.code - %div.highlighted-data{ class: user_color_scheme } - .line-numbers + .file-content.code.js-syntax-highlight + .line-numbers + - snippet_blob[:snippet_chunks].each do |snippet| + - unless snippet[:data].empty? + - snippet[:data].lines.to_a.size.times do |index| + - offset = defined?(snippet[:start_line]) ? snippet[:start_line] : 1 + - i = index + offset + = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do + %i.fa.fa-link + = i + - unless snippet == snippet_blob[:snippet_chunks].last + %a.diff-line-num + = "." + %pre.code + %code - snippet_blob[:snippet_chunks].each do |snippet| - unless snippet[:data].empty? - - snippet[:data].lines.to_a.size.times do |index| - - offset = defined?(snippet[:start_line]) ? snippet[:start_line] : 1 - - i = index + offset - = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}" do - %i.fa.fa-link - = i + = snippet[:data] - unless snippet == snippet_blob[:snippet_chunks].last %a - = "." - .highlight.term - %pre - %code - - snippet_blob[:snippet_chunks].each do |snippet| - - unless snippet[:data].empty? - = snippet[:data] - - unless snippet == snippet_blob[:snippet_chunks].last - %a - = "..." - - else - .file-content.code - .nothing-here-block Empty file + = "..." + - else + .file-content.code + .nothing-here-block Empty file diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index 2bc98983d672d2f7e91cdadfeb7b9325852e5871..ee242c94db82baac988bfaddd04ccda78698882d 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -1,13 +1,12 @@ -.file-content.code.js-syntax-highlight{ class: user_color_scheme } +.file-content.code.js-syntax-highlight .line-numbers - if blob.data.present? - blob.data.lines.each_index do |index| - offset = defined?(first_line_number) ? first_line_number : 1 - i = index + offset -# We're not using `link_to` because it is too slow once we get to thousands of lines. - %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} + %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} %i.fa.fa-link = i .blob-content{data: {blob_id: blob.id}} - :preserve - #{highlight(blob.name, blob.data)} + = highlight(blob.name, blob.data) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index b1fe36dc21c47bc04da71b80c6441edbaedf8c89..0945b93ed5d94331e8c3d464d10b0c29e676fee2 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -49,6 +49,7 @@ if Gitlab::Metrics.enabled? config.instrument_instance_methods(Gitlab::Shell) config.instrument_methods(Gitlab::Git) + config.instrument_instance_methods(Gitlab::Git::Repository) Gitlab::Git.constants.each do |name| const = Gitlab::Git.const_get(name) diff --git a/config/locales/en.yml b/config/locales/en.yml index f6cfb5efd2ada02443fe7939ad58f9b5922d719e..cedb5e207bd3798f0ff2a8e6651937ba368d2b8a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -8,3 +8,7 @@ en: wrong_size: "is the wrong size (should be %{file_size})" size_too_small: "is too small (should be at least %{file_size})" size_too_big: "is too big (should be at most %{file_size})" + views: + pagination: + previous: "Prev" + next: "Next" diff --git a/db/migrate/20160120172143_add_base_commit_sha_to_merge_request_diffs.rb b/db/migrate/20160120172143_add_base_commit_sha_to_merge_request_diffs.rb new file mode 100644 index 0000000000000000000000000000000000000000..d6c6aa4a4e83d112f6ff044f9549139644d4e69e --- /dev/null +++ b/db/migrate/20160120172143_add_base_commit_sha_to_merge_request_diffs.rb @@ -0,0 +1,5 @@ +class AddBaseCommitShaToMergeRequestDiffs < ActiveRecord::Migration + def change + add_column :merge_request_diffs, :base_commit_sha, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 50ae69bdd0fc2dade238fbf6f95d7c859586e67f..97594011a0269fe06c576e947eee9e6373917e44 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: 20160120130905) do +ActiveRecord::Schema.define(version: 20160120172143) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -494,6 +494,7 @@ ActiveRecord::Schema.define(version: 20160120130905) do t.integer "merge_request_id", null: false t.datetime "created_at" t.datetime "updated_at" + t.string "base_commit_sha" end add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree @@ -729,19 +730,19 @@ ActiveRecord::Schema.define(version: 20160120130905) do t.string "type" t.string "title" t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "active", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "active", null: false t.text "properties" - t.boolean "template", default: false - t.boolean "push_events", default: true - t.boolean "issues_events", default: true - t.boolean "merge_requests_events", default: true - t.boolean "tag_push_events", default: true - t.boolean "note_events", default: true, null: false - t.boolean "build_events", default: false, null: false - t.string "category", default: "common", null: false - t.boolean "default", default: false + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false + t.boolean "build_events", default: false, null: false + t.string "category", default: "common", null: false + t.boolean "default", default: false end add_index "services", ["category"], name: "index_services_on_category", using: :btree @@ -858,7 +859,7 @@ ActiveRecord::Schema.define(version: 20160120130905) do t.boolean "hide_project_limit", default: false t.string "unlock_token" t.datetime "otp_grace_period_started_at" - t.boolean "ldap_email", default: false, null: false + t.boolean "ldap_email", default: false, null: false end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree diff --git a/doc/README.md b/doc/README.md index 7d4f84857e001211ad6cc3ec4b17a5b2af1398bc..5089e1e70f6c2f9028e17eea110383294c44a5f8 100644 --- a/doc/README.md +++ b/doc/README.md @@ -30,6 +30,7 @@ - [User permissions](ci/permissions/README.md) - [API](ci/api/README.md) - [Triggering builds through the API](ci/triggers/README.md) +- [Build artifacts](ci/build_artifacts/README.md) ### CI Languages @@ -53,6 +54,7 @@ - [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough. - [Install](install/README.md) Requirements, directory structures and installation from source. +- [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. @@ -67,6 +69,8 @@ - [Reply by email](incoming_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails. - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Git LFS configuration](workflow/lfs/lfs_administration.md) +- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. +- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics ## Contributor documentation diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md new file mode 100644 index 0000000000000000000000000000000000000000..c27cf1812dc2a4a5a4b7d026738e88ae1f4d4e46 --- /dev/null +++ b/doc/administration/housekeeping.md @@ -0,0 +1,22 @@ +# Housekeeping + +_**Note:** This feature was [introduced][ce-2371] in GitLab 8.4_ + +--- + +The housekeeping function runs [`git gc`][man] on the current project Git +repository. + +`git gc` runs a number of housekeeping tasks, such as compressing file +revisions (to reduce disk space and increase performance) and removing +unreachable objects which may have been created from prior invocations of +`git add`. + +You can find this option under your **[Project] > Settings**. + +--- + + + +[ce-2371]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2371 "Housekeeping merge request" +[man]: https://www.kernel.org/pub/software/scm/git/docs/git-gc.html "git gc man page" diff --git a/doc/administration/img/housekeeping_settings.png b/doc/administration/img/housekeeping_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..f7c5bc44367e82ed3a62faa3685c50612f8fb315 Binary files /dev/null and b/doc/administration/img/housekeeping_settings.png differ diff --git a/doc/administration/restart_gitlab.md b/doc/administration/restart_gitlab.md new file mode 100644 index 0000000000000000000000000000000000000000..483060395dd2fc996f90d54744f137a580a5cf03 --- /dev/null +++ b/doc/administration/restart_gitlab.md @@ -0,0 +1,145 @@ +# How to restart GitLab + +Depending on how you installed GitLab, there are different methods to restart +its service(s). + +If you want the TL;DR versions, jump to: + +- [Omnibus GitLab restart](#omnibus-gitlab-restart) +- [Omnibus GitLab reconfigure](#omnibus-gitlab-reconfigure) +- [Source installation restart](#installations-from-source) + +## Omnibus installations + +If you have used the [Omnibus packages][omnibus-dl] to install GitLab, then +you should already have `gitlab-ctl` in your `PATH`. + +`gitlab-ctl` interacts with the Omnibus packages and can be used to restart the +GitLab Rails application (Unicorn) as well as the other components, like: + +- GitLab Workhorse +- Sidekiq +- PostgreSQL (if you are using the bundled one) +- NGINX (if you are using the bundled one) +- Redis (if you are using the bundled one) +- [Mailroom][] +- Logrotate + +### Omnibus GitLab restart + +There may be times in the documentation where you will be asked to _restart_ +GitLab. In that case, you need to run the following command: + +```bash +sudo gitlab-ctl restart +``` + +The output should be similar to this: + +``` +ok: run: gitlab-workhorse: (pid 11291) 1s +ok: run: logrotate: (pid 11299) 0s +ok: run: mailroom: (pid 11306) 0s +ok: run: nginx: (pid 11309) 0s +ok: run: postgresql: (pid 11316) 1s +ok: run: redis: (pid 11325) 0s +ok: run: sidekiq: (pid 11331) 1s +ok: run: unicorn: (pid 11338) 0s +``` + +To restart a component separately, you can append its service name to the +`restart` command. For example, to restart **only** NGINX you would run: + +```bash +sudo gitlab-ctl restart nginx +``` + +To check the status of GitLab services, run: + +```bash +sudo gitlab-ctl status +``` + +Notice that all services say `ok: run`. + +Sometimes, components time out during the restart and sometimes they get stuck. +In that case, you can use `gitlab-ctl kill <service>` to send the `SIGKILL` +signal to the service, for example `sidekiq`. After that, a restart should +perform fine. + +As a last resort, you can try to +[reconfigure GitLab](#omnibus-gitlab-reconfigure) instead. + +### Omnibus GitLab reconfigure + +There may be times in the documentation where you will be asked to _reconfigure_ +GitLab. Remember that this method applies only for the Omnibus packages. + +Reconfigure Omnibus GitLab with: + +```bash +sudo gitlab-ctl reconfigure +``` + +Reconfiguring GitLab should occur in the event that something in its +configuration (`/etc/gitlab/gitlab.rb`) has changed. + +When you run this command, [Chef], the underlying configuration management +application that powers Omnibus GitLab, will make sure that all directories, +permissions, services, etc., are in place and in the same shape that they were +initially shipped. + +It will also restart GitLab components where needed, if any of their +configuration files have changed. + +If you manually edit any files in `/var/opt/gitlab` that are managed by Chef, +running reconfigure will revert the changes AND restart the services that +depend on those files. + +## Installations from source + +If you have followed the official installation guide to [install GitLab from +source][install], run the following command to restart GitLab: + +``` +sudo service gitlab restart +``` + +The output should be similar to this: + +``` +Shutting down GitLab Unicorn +Shutting down GitLab Sidekiq +Shutting down GitLab Workhorse +Shutting down GitLab MailRoom +... +GitLab is not running. +Starting GitLab Unicorn +Starting GitLab Sidekiq +Starting GitLab Workhorse +Starting GitLab MailRoom +... +The GitLab Unicorn web server with pid 28059 is running. +The GitLab Sidekiq job dispatcher with pid 28176 is running. +The GitLab Workhorse with pid 28122 is running. +The GitLab MailRoom email processor with pid 28114 is running. +GitLab and all its components are up and running. +``` + +This should restart Unicorn, Sidekiq, GitLab Workhorse and [Mailroom][] +(if enabled). The init service file that does all the magic can be found on +your server in `/etc/init.d/gitlab`. + +--- + +If you are using other init systems, like systemd, you can check the +[GitLab Recipes][gl-recipes] repository for some unofficial services. These are +**not** officially supported so use them at your own risk. + + +[omnibus-dl]: https://about.gitlab.com/downloads/ "Download the Omnibus packages" +[install]: ../install/installation.md "Documentation to install GitLab from source" +[mailroom]: ../incoming_email/README.md "Used for replying by email in GitLab issues and merge requests" +[chef]: https://www.chef.io/chef/ "Chef official website" +[src-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab "GitLab init service file" +[gl-recipes]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/init "GitLab Recipes repository" diff --git a/doc/api/commits.md b/doc/api/commits.md index 93d62b751e66ab0743e7ab5a0fa3b324952f371d..e4d436b8e52179882656f2f5a9fbc8642be5858f 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -8,10 +8,16 @@ Get a list of repository commits in a project. GET /projects/:id/repository/commits ``` -Parameters: +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch | + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits" +``` -- `id` (required) - The ID of a project -- `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch +Example response: ```json [ @@ -48,8 +54,16 @@ GET /projects/:id/repository/commits/:sha Parameters: -- `id` (required) - The ID of a project -- `sha` (required) - The commit hash or name of a repository branch or tag +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `sha` | string | yes | The commit hash or name of a repository branch or tag | + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master +``` + +Example response: ```json { @@ -79,8 +93,16 @@ GET /projects/:id/repository/commits/:sha/diff Parameters: -- `id` (required) - The ID of a project -- `sha` (required) - The name of a repository branch or tag or if not given the default branch +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `sha` | string | yes | The commit hash or name of a repository branch or tag | + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/diff" +``` + +Example response: ```json [ @@ -107,8 +129,16 @@ GET /projects/:id/repository/commits/:sha/comments Parameters: -- `id` (required) - The ID of a project -- `sha` (required) - The name of a repository branch or tag or if not given the default branch +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `sha` | string | yes | The commit hash or name of a repository branch or tag | + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/comments" +``` + +Example response: ```json [ @@ -128,39 +158,65 @@ Parameters: ## Post comment to commit -Adds a comment to a commit. Optionally you can post comments on a specific line of a commit. Therefor both `path`, `line_new` and `line_old` are required. +Adds a comment to a commit. + +In order to post a comment in a particular line of a particular file, you must +specify the full commit SHA, the `path`, the `line` and `line_type` should be +`new`. + +The comment will be added at the end of the last commit if at least one of the +cases below is valid: + +- the `sha` is instead a branch or a tag and the `line` or `path` are invalid +- the `line` number is invalid (does not exist) +- the `path` is invalid (does not exist) + +In any of the above cases, the response of `line`, `line_type` and `path` is +set to `null`. ``` POST /projects/:id/repository/commits/:sha/comments ``` -Parameters: +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `sha` | string | yes | The commit SHA or name of a repository branch or tag | +| `note` | string | yes | The text of the comment | +| `path` | string | no | The file path relative to the repository | +| `line` | integer | no | The line number where the comment should be placed | +| `line_type` | string | no | The line type. Takes `new` or `old` as arguments | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -F "note=Nice picture man\!" -F "path=dudeism.md" -F "line=11" -F "line_type=new" https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/comments +``` -- `id` (required) - The ID of a project -- `sha` (required) - The name of a repository branch or tag or if not given the default branch -- `note` (required) - Text of comment -- `path` (optional) - The file path -- `line` (optional) - The line number -- `line_type` (optional) - The line type (new or old) +Example response: ```json { - "author": { - "id": 1, - "username": "admin", - "email": "admin@local.host", - "name": "Administrator", - "blocked": false, - "created_at": "2012-04-29T08:46:00Z" - }, - "note": "text1", - "path": "example.rb", - "line": 5, - "line_type": "new" + "author" : { + "web_url" : "https://gitlab.example.com/u/thedude", + "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png", + "username" : "thedude", + "state" : "active", + "name" : "Jeff Lebowski", + "id" : 28 + }, + "created_at" : "2016-01-19T09:44:55.600Z", + "line_type" : "new", + "path" : "dudeism.md", + "line" : 11, + "note" : "Nice picture man!" } ``` -## Get the status of a commit +## Commit status + +Since GitLab 8.1, this is the new commit status API. The documentation in +[ci/api/commits](../ci/api/commits.md) is deprecated. + +### Get the status of a commit Get the statuses of a commit in a project. @@ -168,75 +224,116 @@ Get the statuses of a commit in a project. GET /projects/:id/repository/commits/:sha/statuses ``` -Parameters: +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project +| `sha` | string | yes | The commit SHA +| `ref_name`| string | no | The name of a repository branch or tag or, if not given, the default branch +| `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test` +| `name` | string | no | Filter by [job name](../ci/yaml/README.md#jobs), e.g., `bundler:audit` +| `all` | boolean | no | Return all statuses, not only the latest ones + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/statuses +``` -- `id` (required) - The ID of a project -- `sha` (required) - The commit SHA -- `ref` (optional) - Filter by ref name, it can be branch or tag -- `stage` (optional) - Filter by stage -- `name` (optional) - Filer by status name, eg. jenkins -- `all` (optional) - The flag to return all statuses, not only latest ones +Example response: ```json [ - { - "id": 13, - "sha": "b0b3a907f41409829b307a28b82fdbd552ee5a27", - "ref": "test", - "status": "success", - "name": "ci/jenkins", - "target_url": "http://jenkins/project/url", - "description": "Jenkins success", - "created_at": "2015-10-12T09:47:16.250Z", - "started_at": "2015-10-12T09:47:16.250Z", - "finished_at": "2015-10-12T09:47:16.262Z", - "author": { - "id": 1, - "username": "admin", - "email": "admin@local.host", - "name": "Administrator", - "blocked": false, - "created_at": "2012-04-29T08:46:00Z" - } - } + ... + + { + "status" : "pending", + "created_at" : "2016-01-19T08:40:25.934Z", + "started_at" : null, + "name" : "bundler:audit", + "allow_failure" : true, + "author" : { + "username" : "thedude", + "state" : "active", + "web_url" : "https://gitlab.example.com/u/thedude", + "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png", + "id" : 28, + "name" : "Jeff Lebowski" + }, + "description" : null, + "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8", + "target_url" : "https://gitlab.example.com/thedude/gitlab-ce/builds/91", + "finished_at" : null, + "id" : 91, + "ref" : "master" + }, + { + "started_at" : null, + "name" : "flay", + "allow_failure" : false, + "status" : "pending", + "created_at" : "2016-01-19T08:40:25.832Z", + "target_url" : "https://gitlab.example.com/thedude/gitlab-ce/builds/90", + "id" : 90, + "finished_at" : null, + "ref" : "master", + "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8", + "author" : { + "id" : 28, + "name" : "Jeff Lebowski", + "username" : "thedude", + "web_url" : "https://gitlab.example.com/u/thedude", + "state" : "active", + "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png" + }, + "description" : null + }, + + ... ] ``` -## Post the status to commit +### Post the build status to a commit -Adds or updates a status of a commit. +Adds or updates a build status of a commit. ``` POST /projects/:id/statuses/:sha ``` -- `id` (required) - The ID of a project -- `sha` (required) - The commit SHA -- `state` (required) - The state of the status. Can be: pending, running, success, failed, canceled -- `ref` (optional) - The ref (branch or tag) to which the status refers -- `name` or `context` (optional) - The label to differentiate this status from the status of other systems. Default: "default" -- `target_url` (optional) - The target URL to associate with this status -- `description` (optional) - The short description of the status +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project +| `sha` | string | yes | The commit SHA +| `state` | string | yes | The state of the status. Can be one of the following: `pending`, `running`, `success`, `failed`, `canceled` +| `ref` | string | no | The `ref` (branch or tag) to which the status refers +| `name` or `context` | string | no | The label to differentiate this status from the status of other systems. Default value is `default` +| `target_url` | string | no | The target URL to associate with this status +| `description` | string | no | The short description of the status + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/statuses/18f3e63d05582537db6d183d9d557be09e1f90c8?state=success" +``` + +Example response: ```json { - "id": 13, - "sha": "b0b3a907f41409829b307a28b82fdbd552ee5a27", - "ref": "test", - "status": "success", - "name": "ci/jenkins", - "target_url": "http://jenkins/project/url", - "description": "Jenkins success", - "created_at": "2015-10-12T09:47:16.250Z", - "started_at": "2015-10-12T09:47:16.250Z", - "finished_at": "2015-10-12T09:47:16.262Z", - "author": { - "id": 1, - "username": "admin", - "email": "admin@local.host", - "name": "Administrator", - "blocked": false, - "created_at": "2012-04-29T08:46:00Z" - } + "author" : { + "web_url" : "https://gitlab.example.com/u/thedude", + "name" : "Jeff Lebowski", + "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png", + "username" : "thedude", + "state" : "active", + "id" : 28 + }, + "name" : "default", + "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8", + "status" : "success", + "description" : null, + "id" : 93, + "target_url" : null, + "ref" : null, + "started_at" : null, + "created_at" : "2016-01-19T09:05:50.355Z", + "allow_failure" : false, + "finished_at" : "2016-01-19T09:05:50.365Z" } ``` diff --git a/doc/ci/README.md b/doc/ci/README.md index 4cdd2e1ad3394017887160e365557e2f7028a528..5886829be51084594c0637561b464f89d2866e3c 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -12,6 +12,7 @@ * [Using Variables](variables/README.md) * [Using SSH keys](ssh_keys/README.md) * [Triggering builds through the API](triggers/README.md) +* [Build artifacts](build_artifacts/README.md) ### Languages diff --git a/doc/ci/api/commits.md b/doc/ci/api/commits.md index 4df7afc6c529e916ce37b3e2b30cf6f7685e1daf..871de7abcce18ccd03451140cca3b49c40aa08c5 100644 --- a/doc/ci/api/commits.md +++ b/doc/ci/api/commits.md @@ -1,5 +1,12 @@ # Commits API +**DEPRECATED** + +Since GitLab 8.1, there is a new commit status API. Please see the [revised +documentation](../../api/commits.md#commit-status). + +--- + __Authentication is done by GitLab CI project token__ ## Commits diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md new file mode 100644 index 0000000000000000000000000000000000000000..61418cef2c6de147d8b16eeae530172ed6f2ca8d --- /dev/null +++ b/doc/ci/build_artifacts/README.md @@ -0,0 +1,171 @@ +# Introduction to build artifacts + +Artifacts is a list of files and directories which are attached to a build +after it completes successfully. + +Since GitLab 8.2 and [GitLab Runner] 0.7.0, build artifacts that are created by +GitLab Runner are uploaded to GitLab and are downloadable as a single archive +(`tar.gz`) using the GitLab UI. + +Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format +changed to `ZIP`, and it is now possible to browse its contents, with the added +ability of downloading the files separately. + +## Enabling build artifacts + +_If you are searching for ways to use artifacts, jump to +[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._ + +The artifacts feature is enabled by default in all GitLab installations. +To disable it site-wide, follow the steps below. + +--- + +**In Omnibus installations:** + +1. Edit `/etc/gitlab/gitlab.rb` and add the following line: + + ```ruby + gitlab_rails['artifacts_enabled'] = false + ``` + +1. Save the file and [reconfigure GitLab][] for the changes to take effect. + +--- + +**In installations from source:** + +1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines: + + ```yaml + artifacts: + enabled: false + ``` + +1. Save the file and [restart GitLab][] for the changes to take effect. + +## Defining artifacts in `.gitlab-ci.yml` + +A simple example of using the artifacts definition in `.gitlab-ci.yml` would be +the following: + +```yaml +pdf: + script: xelatex mycv.tex + artifacts: + paths: + - mycv.pdf +``` + +A job named `pdf` calls the `xelatex` command in order to build a pdf file from +the latex source file `mycv.tex`. We then define the `artifacts` paths which in +turn are defined with the `paths` keyword. All paths to files and directories +are relative to the repository that was cloned during the build. + +For more examples on artifacts, follow the +[separate artifacts yaml documentation](../yaml/README.md#artifacts). + +## Storing build artifacts + +After a successful build, GitLab Runner uploads an archive containing the build +artifacts to GitLab. + +To change the location where the artifacts are stored, follow the steps below. + +--- + +**In Omnibus installations:** + +_The artifacts are stored by default in +`/var/opt/gitlab/gitlab-rails/shared/artifacts`._ + +1. To change the storage path for example to `/mnt/storage/artifacts`, edit + `/etc/gitlab/gitlab.rb` and add the following line: + + ```ruby + gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts" + ``` + +1. Save the file and [reconfigure GitLab][] for the changes to take effect. + +--- + +**In installations from source:** + +_The artifacts are stored by default in +`/home/git/gitlab/shared/artifacts`._ + +1. To change the storage path for example to `/mnt/storage/artifacts`, edit + `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines: + + ```yaml + artifacts: + enabled: true + path: /mnt/storage/artifacts + ``` + +1. Save the file and [restart GitLab][] for the changes to take effect. + +## Browsing build artifacts + +When GitLab receives an artifacts archive, an archive metadata file is also +generated. This metadata file describes all the entries that are located in the +artifacts archive itself. The metadata file is in a binary format, with +additional GZIP compression. + +GitLab does not extract the artifacts archive in order to save space, memory +and disk I/O. It instead inspects the metadata file which contains all the +relevant information. This is especially important when there is a lot of +artifacts, or an archive is a very large file. + +--- + +After a successful build, if you visit the build's specific page, you can see +that there are two buttons. + +One is for downloading the artifacts archive and the other for browsing its +contents. + + + +--- + +The archive browser shows the name and the actual file size of each file in the +archive. If your artifacts contained directories, then you are also able to +browse inside them. + +Below you can see an image of three different file formats, as well as two +directories. + + + +--- + +## Downloading build artifacts + +If you need to download the whole archive, there are buttons in various places +inside GitLab that make that possible. + +1. While on the builds page, you can see the download icon for each build's + artifacts archive in the right corner + +1. While inside a specific build, you are presented with a download button + along with the one that browses the archive + +1. And finally, when browsing and archive you can see the download button at + the top right corner + +--- + +Note that GitLab does not extract the entire artifacts archive to send just a +single file to the user. + +When clicking on a specific file, [GitLab Workhorse] extracts it from the +archive and the download begins. + +This implementation saves space, memory and disk I/O. + +[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner "GitLab Runner repository" +[reconfigure gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation" +[restart gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation" +[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository" diff --git a/doc/ci/build_artifacts/img/build_artifacts_browser.png b/doc/ci/build_artifacts/img/build_artifacts_browser.png new file mode 100644 index 0000000000000000000000000000000000000000..73ed4eeb92781c798c3461283ca84d34d462c61c Binary files /dev/null and b/doc/ci/build_artifacts/img/build_artifacts_browser.png differ diff --git a/doc/ci/build_artifacts/img/build_artifacts_browser_button.png b/doc/ci/build_artifacts/img/build_artifacts_browser_button.png new file mode 100644 index 0000000000000000000000000000000000000000..f5d15bc3e7d1bf1e5db53efb2e4ef7e771576a33 Binary files /dev/null and b/doc/ci/build_artifacts/img/build_artifacts_browser_button.png differ diff --git a/doc/ci/languages/php.md b/doc/ci/languages/php.md index dacb67fa3ff4cfaf209fe96c0161b13d13730a91..77f9fae5bb68a8be45a6ae0b959e2dbf72ad6ecc 100644 --- a/doc/ci/languages/php.md +++ b/doc/ci/languages/php.md @@ -12,7 +12,7 @@ configuration from the developer. To overcome this we will be using the official [PHP docker image][php-hub] that can be found in Docker Hub. This will allow us to test PHP projects against different versions of PHP. -However, not everything is plug 'n' play, you still need to onfigure some +However, not everything is plug 'n' play, you still need to configure some things manually. As with every build, you need to create a valid `.gitlab-ci.yml` describing the diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index b99ea25a3fe33ef74ceef3a9a4202452e897e95e..862cacda586bb21fb56a49cd5c93261295799921 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -56,7 +56,7 @@ export CI_SERVER_VERSION="" ``` ### YAML-defined variables -**This feature requires GitLab Runner 0.5.0 or higher** +**This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher ** GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build environment. The variables are stored in repository and are meant to store non-sensitive project configuration, ie. RAILS_ENV or DATABASE_URL. diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index fd0d49de4e438ba654ee160276733cb38768069d..c8d5ecb8eefaf3585e3a3e237bf0a25180b1551e 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -135,10 +135,9 @@ thus allowing to fine tune them. ### cache `cache` is used to specify a list of files and directories which should be -cached between builds. Caches are stored according to the branch/ref and the -job name. They are not currently shared between different job names or between -branches/refs, which means that caching will benefit you if you push subsequent -commits to an existing feature branch. +cached between builds. + +**By default the caching is enabled per-job and per-branch.** If `cache` is defined outside the scope of the jobs, it means it is set globally and all jobs will use its definition. @@ -152,6 +151,64 @@ cache: - binaries/ ``` +#### cache:key + +_**Note:** Introduced in GitLab Runner v1.0.0._ + +The `key` directive allows you to define the affinity of caching +between jobs, allowing to have a single cache for all jobs, +cache per-job, cache per-branch or any other way you deem proper. + +This allows you to fine tune caching, allowing you to cache data between +different jobs or even different branches. + +The `cache:key` variable can use any of the [predefined variables](../variables/README.md). + +--- + +**Example configurations** + +To enable per-job caching: + +```yaml +cache: + key: "$CI_BUILD_NAME" + untracked: true +``` + +To enable per-branch caching: + +```yaml +cache: + key: "$CI_BUILD_REF_NAME" + untracked: true +``` + +To enable per-job and per-branch caching: + +```yaml +cache: + key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME" + untracked: true +``` + +To enable per-branch and per-stage caching: + +```yaml +cache: + key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME" + untracked: true +``` + +If you use **Windows Batch** to run your shell scripts you need to replace +`$` with `%`: + +```yaml +cache: + key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%" + untracked: true +``` + ## Jobs `.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 0bd32b7820169e87fe7dd632a63f7958f5fcb8ac..caaa4032db26ff7fd1e562fb886655c5f6f650b5 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -103,6 +103,23 @@ Inside the document: `_**Note:** This feature was introduced in GitLab EE 8.3_`. Otherwise, leave this mention out +## References + +- **GitLab Restart:** + There are many cases that a restart/reconfigure of GitLab is required. To + avoid duplication, link to the special document that can be found in + [`doc/administration/restart_gitlab.md`][doc-restart]. Usually the text will + read like: + + ``` + Save the file and [reconfigure GitLab](../administration/restart_gitlab.md) + for the changes to take effect. + ``` + If the document you are editing resides in a place other than the GitLab CE/EE + `doc/` directory, instead of the relative link, use the full path: + `http://doc.gitlab.com/ce/administration/restart_gitlab.html`. + Replace `reconfigure` with `restart` where appropriate. + ## API Here is a list of must-have items. Use them in the exact order that appears @@ -229,3 +246,4 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "restricted_signup_domai [cURL]: http://curl.haxx.se/ "cURL website" [single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html [gfm]: http://doc.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation" +[doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation" diff --git a/doc/install/installation.md b/doc/install/installation.md index 00030729a4b5e78cfbe6c86e3274493506f8d69c..4772ed3c566fd6a9d8d8e6c75b62f4ebe1755089 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -107,9 +107,16 @@ Then select 'Internet Site' and press enter to confirm the hostname. ## 2. Ruby -The use of Ruby version managers such as [RVM](https://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby. +_**Note:** The current supported Ruby versions are 2.1.x. Ruby 2.2 and 2.3 are +currently not supported._ -Remove the old Ruby 1.8 if present +The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab +in production, frequently leads to hard to diagnose problems. For example, +GitLab Shell is called from OpenSSH, and having a version manager can prevent +pushing and pulling over SSH. Version managers are not supported and we strongly +advise everyone to follow the instructions below to use a system Ruby. + +Remove the old Ruby 1.8 if present: sudo apt-get remove ruby1.8 @@ -555,3 +562,7 @@ this is likely due to an outdated Nginx or Apache configuration, or a missing or misconfigured gitlab-workhorse instance. Double-check that you've [installed Go](#3-go), [installed gitlab-workhorse](#install-gitlab-workhorse), and correctly [configured Nginx](#site-configuration). + +[RVM]: https://rvm.io/ "RVM Homepage" +[rbenv]: https://github.com/sstephenson/rbenv "rbenv on GitHub" +[chruby]: https://github.com/postmodern/chruby "chruby on GitHub" diff --git a/doc/install/requirements.md b/doc/install/requirements.md index c0ccdd374586e29ae5ec53939c5fde3b968c3df0..c0425f27ab17e3f9a7e6db223d552fab372f51de 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -32,15 +32,18 @@ Please consider using a virtual machine to run GitLab. ## Ruby versions -GitLab requires Ruby (MRI) 2.1 +GitLab requires Ruby (MRI) 2.1.x and currently does not work with versions 2.2 +and 2.3. + You will have to use the standard MRI implementation of Ruby. -We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab needs several Gems that have native extensions. +We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab +needs several Gems that have native extensions. ## Hardware requirements ### Storage -The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least as much free space as all your repos combined take up. +The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least as much free space as all your repos combined take up. If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them. @@ -109,4 +112,4 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o - Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/)) - Safari 7+ (known problem: required fields in html5 do not work) - Opera (Latest released version) -- Internet Explorer (IE) 10+ but please make sure that you have the `Compatibility View` mode disabled. \ No newline at end of file +- Internet Explorer (IE) 10+ but please make sure that you have the `Compatibility View` mode disabled. diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md new file mode 100644 index 0000000000000000000000000000000000000000..b856e7935a3955df647f00abfed40fa1f4efc3aa --- /dev/null +++ b/doc/monitoring/performance/gitlab_configuration.md @@ -0,0 +1,39 @@ +# GitLab Configuration + +GitLab Performance Monitoring is disabled by default. To enable it and change any of its +settings, navigate to the Admin area in **Settings > Metrics** +(`/admin/application_settings`). + +The minimum required settings you need to set are the InfluxDB host and port. +Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the +changes. + +--- + + + +--- + +Finally, a restart of all GitLab processes is required for the changes to take +effect: + +```bash +# For Omnibus installations +sudo gitlab-ctl restart + +# For installations from source +sudo service gitlab restart +``` + +## Pending Migrations + +When any migrations are pending, the metrics are disabled until the migrations +have been performed. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [InfluxDB Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) diff --git a/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png b/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..14d82b6ac98c5c942b93a83d17f6f82987a5c7b3 Binary files /dev/null and b/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png differ diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md new file mode 100644 index 0000000000000000000000000000000000000000..3a2b598b78f5be80b8469adf0c4c6925a3d10b29 --- /dev/null +++ b/doc/monitoring/performance/influxdb_configuration.md @@ -0,0 +1,192 @@ +# InfluxDB Configuration + +The default settings provided by [InfluxDB] are not sufficient for a high traffic +GitLab environment. The settings discussed in this document are based on the +settings GitLab uses for GitLab.com, depending on your own needs you may need to +further adjust them. + +If you are intending to run InfluxDB on the same server as GitLab, make sure +you have plenty of RAM since InfluxDB can use quite a bit depending on traffic. + +Unless you are going with a budget setup, it's advised to run it separately. + +## Requirements + +- InfluxDB 0.9.5 or newer +- A fairly modern version of Linux +- At least 4GB of RAM +- At least 10GB of storage for InfluxDB data + +Note that the RAM and storage requirements can differ greatly depending on the +amount of data received/stored. To limit the amount of stored data users can +look into [InfluxDB Retention Policies][influxdb-retention]. + +## Installation + +Installing InfluxDB is out of the scope of this document. Please refer to the +[InfluxDB documentation]. + +## InfluxDB Server Settings + +Since InfluxDB has many settings that users may wish to customize themselves +(e.g. what port to run InfluxDB on), we'll only cover the essentials. + +The configuration file in question is usually located at +`/etc/influxdb/influxdb.conf`. Whenever you make a change in this file, +InfluxDB needs to be restarted. + +### Storage Engine + +InfluxDB comes with different storage engines and as of InfluxDB 0.9.5 a new +storage engine is available, called [TSM Tree]. All users **must** use the new +`tsm1` storage engine as this [will be the default engine][tsm1-commit] in +upcoming InfluxDB releases. + +Make sure you have the following in your configuration file: + +``` +[data] + dir = "/var/lib/influxdb/data" + engine = "tsm1" +``` + +### Admin Panel + +Production environments should have the InfluxDB admin panel **disabled**. This +feature can be disabled by adding the following to your InfluxDB configuration +file: + +``` +[admin] + enabled = false +``` + +### HTTP + +HTTP is required when using the [InfluxDB CLI] or other tools such as Grafana, +thus it should be enabled. When enabling make sure to _also_ enable +authentication: + +``` +[http] + enabled = true + auth-enabled = true +``` + +_**Note:** Before you enable authentication, you might want to [create an +admin user](#create-a-new-admin-user)._ + +### UDP + +GitLab writes data to InfluxDB via UDP and thus this must be enabled. Enabling +UDP can be done using the following settings: + +``` +[[udp]] + enabled = true + bind-address = ":8089" + database = "gitlab" + batch-size = 1000 + batch-pending = 5 + batch-timeout = "1s" + read-buffer = 209715200 +``` + +This does the following: + +1. Enable UDP and bind it to port 8089 for all addresses. +2. Store any data received in the "gitlab" database. +3. Define a batch of points to be 1000 points in size and allow a maximum of + 5 batches _or_ flush them automatically after 1 second. +4. Define a UDP read buffer size of 200 MB. + +One of the most important settings here is the UDP read buffer size as if this +value is set too low, packets will be dropped. You must also make sure the OS +buffer size is set to the same value, the default value is almost never enough. + +To set the OS buffer size to 200 MB, on Linux you can run the following command: + +```bash +sysctl -w net.core.rmem_max=209715200 +``` + +To make this permanent, add the following to `/etc/sysctl.conf` and restart the +server: + +```bash +net.core.rmem_max=209715200 +``` + +It is **very important** to make sure the buffer sizes are large enough to +handle all data sent to InfluxDB as otherwise you _will_ lose data. The above +buffer sizes are based on the traffic for GitLab.com. Depending on the amount of +traffic, users may be able to use a smaller buffer size, but we highly recommend +using _at least_ 100 MB. + +When enabling UDP, users should take care to not expose the port to the public, +as doing so will allow anybody to write data into your InfluxDB database (as +[InfluxDB's UDP protocol][udp] doesn't support authentication). We recommend either +whitelisting the allowed IP addresses/ranges, or setting up a VLAN and only +allowing traffic from members of said VLAN. + +## Create a new admin user + +If you want to [enable authentication](#http), you might want to [create an +admin user][influx-admin]: + +``` +influx -execute "CREATE USER jeff WITH PASSWORD '1234' WITH ALL PRIVILEGES" +``` + +## Create the `gitlab` database + +Once you get InfluxDB up and running, you need to create a database for GitLab. +Make sure you have changed the [storage engine](#storage-engine) to `tsm1` +before creating a database. + +_**Note:** If you [created an admin user](#create-a-new-admin-user) and enabled +[HTTP authentication](#http), remember to append the username (`-username <username>`) +and password (`-password <password>`) you set earlier to the commands below._ + +Run the following command to create a database named `gitlab`: + +```bash +influx -execute 'CREATE DATABASE gitlab' +``` + +The name **must** be `gitlab`, do not use any other name. + +Next, make sure that the database was successfully created: + +```bash +influx -execute 'SHOW DATABASES' +``` + +The output should be similar to: + +``` +name: databases +--------------- +name +_internal +gitlab +``` + +That's it! Now your GitLab instance should send data to InfluxDB. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) + +[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management +[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ +[influxdb cli]: https://docs.influxdata.com/influxdb/v0.9/tools/shell/ +[udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ +[influxdb]: https://influxdata.com/time-series-platform/influxdb/ +[tsm tree]: https://influxdata.com/blog/new-storage-engine-time-structured-merge-tree/ +[tsm1-commit]: https://github.com/influxdata/influxdb/commit/15d723dc77651bac83e09e2b1c94be480966cb0d +[influx-admin]: https://docs.influxdata.com/influxdb/v0.9/administration/authentication_and_authorization/#create-a-new-admin-user diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md new file mode 100644 index 0000000000000000000000000000000000000000..a5a8aebd2d1381643cb27f0c1375a9e415bfa969 --- /dev/null +++ b/doc/monitoring/performance/influxdb_schema.md @@ -0,0 +1,87 @@ +# InfluxDB Schema + +The following measurements are currently stored in InfluxDB: + +- `PROCESS_file_descriptors` +- `PROCESS_gc_statistics` +- `PROCESS_memory_usage` +- `PROCESS_method_calls` +- `PROCESS_object_counts` +- `PROCESS_transactions` +- `PROCESS_views` + +Here, `PROCESS` is replaced with either `rails` or `sidekiq` depending on the +process type. In all series, any form of duration is stored in milliseconds. + +## PROCESS_file_descriptors + +This measurement contains the number of open file descriptors over time. The +value field `value` contains the number of descriptors. + +## PROCESS_gc_statistics + +This measurement contains Ruby garbage collection statistics such as the amount +of minor/major GC runs (relative to the last sampling interval), the time spent +in garbage collection cycles, and all fields/values returned by `GC.stat`. + +## PROCESS_memory_usage + +This measurement contains the process' memory usage (in bytes) over time. The +value field `value` contains the number of bytes. + +## PROCESS_method_calls + +This measurement contains the methods called during a transaction along with +their duration, and a name of the transaction action that invoked the method (if +available). The method call duration is stored in the value field `duration`, +while the method name is stored in the tag `method`. The tag `action` contains +the full name of the transaction action. Both the `method` and `action` fields +are in the following format: + +``` +ClassName#method_name +``` + +For example, a method called by the `show` method in the `UsersController` class +would have `action` set to `UsersController#show`. + +## PROCESS_object_counts + +This measurement is used to store retained Ruby objects (per class) and the +amount of retained objects. The number of objects is stored in the `count` value +field while the class name is stored in the `type` tag. + +## PROCESS_transactions + +This measurement is used to store basic transaction details such as the time it +took to complete a transaction, how much time was spent in SQL queries, etc. The +following value fields are available: + +| Value | Description | +| ----- | ----------- | +| `duration` | The total duration of the transaction | +| `allocated_memory` | The amount of bytes allocated while the transaction was running. This value is only reliable when using single-threaded application servers | +| `method_duration` | The total time spent in method calls | +| `sql_duration` | The total time spent in SQL queries | +| `view_duration` | The total time spent in views | + +## PROCESS_views + +This measurement is used to store view rendering timings for a transaction. The +following value fields are available: + +| Value | Description | +| ----- | ----------- | +| `duration` | The rendering time of the view | +| `view` | The path of the view, relative to the application's root directory | + +The `action` tag contains the action name of the transaction that rendered the +view. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Configuration](influxdb_configuration.md) diff --git a/doc/monitoring/performance/introduction.md b/doc/monitoring/performance/introduction.md new file mode 100644 index 0000000000000000000000000000000000000000..f2460d3130203f33379701a01a408f54a246ab44 --- /dev/null +++ b/doc/monitoring/performance/introduction.md @@ -0,0 +1,64 @@ +# GitLab Performance Monitoring + +GitLab comes with its own application performance measuring system as of GitLab +8.4, simply called "GitLab Performance Monitoring". GitLab Performance Monitoring is available in both the +Community and Enterprise editions. + +Apart from this introduction, you are advised to read through the following +documents in order to understand and properly configure GitLab Performance Monitoring: + +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) + +## Introduction to GitLab Performance Monitoring + +GitLab Performance Monitoring makes it possible to measure a wide variety of statistics +including (but not limited to): + +- The time it took to complete a transaction (a web request or Sidekiq job). +- The time spent in running SQL queries and rendering HAML views. +- The time spent executing (instrumented) Ruby methods. +- Ruby object allocations, and retained objects in particular. +- System statistics such as the process' memory usage and open file descriptors. +- Ruby garbage collection statistics. + +Metrics data is written to [InfluxDB][influxdb] over [UDP][influxdb-udp]. Stored +data can be visualized using [Grafana][grafana] or any other application that +supports reading data from InfluxDB. Alternatively data can be queried using the +InfluxDB CLI. + +## Metric Types + +Two types of metrics are collected: + +1. Transaction specific metrics. +1. Sampled metrics, collected at a certain interval in a separate thread. + +### Transaction Metrics + +Transaction metrics are metrics that can be associated with a single +transaction. This includes statistics such as the transaction duration, timings +of any executed SQL queries, time spent rendering HAML views, etc. These metrics +are collected for every Rack request and Sidekiq job processed. + +### Sampled Metrics + +Sampled metrics are metrics that can't be associated with a single transaction. +Examples include garbage collection statistics and retained Ruby objects. These +metrics are collected at a regular interval. This interval is made up out of two +parts: + +1. A user defined interval. +1. A randomly generated offset added on top of the interval, the same offset + can't be used twice in a row. + +The actual interval can be anywhere between a half of the defined interval and a +half above the interval. For example, for a user defined interval of 15 seconds +the actual interval can be anywhere between 7.5 and 22.5. The interval is +re-generated for every sampling run instead of being generated once and re-used +for the duration of the process' lifetime. + +[influxdb]: https://influxdata.com/time-series-platform/influxdb/ +[influxdb-udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ +[grafana]: http://grafana.org/ diff --git a/doc/security/img/two_factor_authentication_settings.png b/doc/security/img/two_factor_authentication_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..aa51ce030bb77346686f49633b67c3a2b262481e Binary files /dev/null and b/doc/security/img/two_factor_authentication_settings.png differ diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md index 4e25a1fdc3ff381c271726b968da2e01d0b97f79..8365bdb7b1b38b504cf1cf9db1b6ad154868623d 100644 --- a/doc/security/two_factor_authentication.md +++ b/doc/security/two_factor_authentication.md @@ -20,7 +20,13 @@ In the Admin area under **Settings** (`/admin/application_settings`), look for the "Sign-in Restrictions" area, where you can configure both. If you want 2FA enforcement to take effect on next login, change the grace -period to `0` +period to `0`. + +--- + + + +--- ## Disabling 2FA for everyone @@ -28,11 +34,12 @@ There may be some special situations where you want to disable 2FA for everyone even when forced 2FA is disabled. There is a rake task for that: ``` -# use this command if you've installed GitLab with the Omnibus package +# Omnibus installations sudo gitlab-rake gitlab:two_factor:disable_for_all_users -# if you've installed GitLab from source +# Installations from source sudo -u git -H bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production ``` -**IMPORTANT: this is a permanent and irreversible action. Users will have to reactivate 2FA from scratch if they want to use it again.** +**IMPORTANT: this is a permanent and irreversible action. Users will have to + reactivate 2FA from scratch if they want to use it again.** diff --git a/doc/update/8.3-to-8.4.md b/doc/update/8.3-to-8.4.md index 604939cd73380bfeeff314bdadb5f0a6d26ccdf8..164cbcb53ce80383d4d27f699ddea8326900e4bd 100644 --- a/doc/update/8.3-to-8.4.md +++ b/doc/update/8.3-to-8.4.md @@ -37,7 +37,7 @@ sudo -u git -H git checkout 8-4-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all -sudo -u git -H git checkout v2.6.9 +sudo -u git -H git checkout v2.6.10 ``` ### 5. Update gitlab-workhorse @@ -48,7 +48,7 @@ which should already be on your system from GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout 0.6.0 +sudo -u git -H git checkout 0.6.1 sudo -u git -H make ``` @@ -104,10 +104,7 @@ via [/etc/default/gitlab]. #### Init script -We updated the init script for GitLab in order to pass new -configuration options to gitlab-workhorse. We let gitlab-workhorse -connect to the Rails application via a Unix domain socket and we tell -it where the 'public' directory of GitLab is. +We updated the init script for GitLab in order to set a specific PATH for gitlab-workhorse. ``` cd /home/git/gitlab diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 3651b55f438ee3c9d45e56e6daf69dd5e8c6a3b3..bf62ab4105329e8a671ef681ac2b78c566d8cdbf 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -6,6 +6,7 @@ - [GitLab Flow](gitlab_flow.md) - [Groups](groups.md) - [Keyboard shortcuts](shortcuts.md) +- [File finder](file_finder.md) - [Labels](labels.md) - [Notification emails](notifications.md) - [Project Features](project_features.md) diff --git a/doc/workflow/file_finder.md b/doc/workflow/file_finder.md new file mode 100644 index 0000000000000000000000000000000000000000..b69ae663272859d5c35d03ae8ea41b20f40179db --- /dev/null +++ b/doc/workflow/file_finder.md @@ -0,0 +1,46 @@ +# File finder + +_**Note:** This feature was [introduced][gh-9889] in GitLab 8.4._ + +--- + +The file finder feature allows you to quickly shortcut your way when you are +searching for a file in a repository using the GitLab UI. + +You can find the **Find File** button when in the **Files** section of a +project. + + + +--- + +For those who prefer to keep their fingers on the keyboard, there is a +[shortcut button](shortcuts.md) as well, which you can invoke from _anywhere_ +in a project. + +Press `t` to launch the File search function when in **Issues**, +**Merge requests**, **Milestones**, even the project's settings. + +Start typing what you are searching for and watch the magic happen. With the +up/down arrows, you go up and down the results, with `Esc` you close the search +and go back to **Files**. + +## How it works + +The File finder feature is powered by the [Fuzzy filter] library. + +It implements a fuzzy search with highlight, and tries to provide intuitive +results by recognizing patterns that people use while searching. + +For example, consider the [GitLab CE repository][ce] and that we want to open +the `app/controllers/admin/deploy_keys_controller.rb` file. + +Using fuzzy search, we start by typing letters that get us closer to the file. + +**Protip:** To narrow down your search, include `/` in your search terms. + + + +[gh-9889]: https://github.com/gitlabhq/gitlabhq/pull/9889 "File finder pull request" +[fuzzy filter]: https://github.com/jeancroy/fuzzaldrin-plus "fuzzaldrin-plus on GitHub" +[ce]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master "GitLab CE repository" diff --git a/doc/workflow/img/file_finder_find_button.png b/doc/workflow/img/file_finder_find_button.png new file mode 100644 index 0000000000000000000000000000000000000000..c5005d0d7cab1799875f75a55f74774560eb9a65 Binary files /dev/null and b/doc/workflow/img/file_finder_find_button.png differ diff --git a/doc/workflow/img/file_finder_find_file.png b/doc/workflow/img/file_finder_find_file.png new file mode 100644 index 0000000000000000000000000000000000000000..58500f4c163fbbe6e7889eb69a975aec95bfe2f3 Binary files /dev/null and b/doc/workflow/img/file_finder_find_file.png differ diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 6b43e7ffedf579323269d3dfdbc7c2c7bde65b6f..2938a22caa2f326b8d40cab402432e8289cc2506 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -117,6 +117,15 @@ Feature: Project Merge Requests And I leave a comment like "Line is wrong" on diff And I switch to the merge request's comments tab Then I should see a discussion has started on diff + And I should see a badge of "1" next to the discussion link + + @javascript + Scenario: I see a new comment on merge request diff from another user in the discussion tab + Given project "Shop" have "Bug NS-05" open merge request with diffs inside + And I visit merge request page "Bug NS-05" + And user "John Doe" leaves a comment like "Line is wrong" on diff + Then I should see a discussion by user "John Doe" has started on diff + And I should see a badge of "1" next to the discussion link @javascript Scenario: I edit a comment on a merge request diff @@ -134,9 +143,11 @@ Feature: Project Merge Requests And I visit merge request page "Bug NS-05" And I click on the Changes tab And I leave a comment like "Line is wrong" on diff + And I should see a badge of "1" next to the discussion link And I delete the comment "Line is wrong" on diff And I click on the Discussion tab Then I should not see any discussion + And I should see a badge of "0" next to the discussion link @javascript Scenario: I comment on a line of a commit in merge request diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 8af635689e0cb59efe1cd346ebcda1f4bfc8e73c..337893e6209fa4bf7f55fd29a3cef13c4967a0b8 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -181,6 +181,15 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps leave_comment "Line is wrong" end + step 'user "John Doe" leaves a comment like "Line is wrong" on diff' do + mr = MergeRequest.find_by(title: "Bug NS-05") + create(:note_on_merge_request_diff, project: project, + noteable_id: mr.id, + author: user_exists("John Doe"), + line_code: sample_commit.line_code, + note: 'Line is wrong') + end + step 'I leave a comment like "Line is wrong" on diff in commit' do click_diff_line(sample_commit.line_code) leave_comment "Line is wrong" @@ -238,6 +247,22 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end end + step 'I should see a discussion by user "John Doe" has started on diff' do + page.within(".notes .discussion") do + page.should have_content "#{user_exists("John Doe").name} started a discussion" + page.should have_content sample_commit.line_code_path + page.should have_content "Line is wrong" + end + end + + step 'I should see a badge of "1" next to the discussion link' do + expect_discussion_badge_to_have_counter("1") + end + + step 'I should see a badge of "0" next to the discussion link' do + expect_discussion_badge_to_have_counter("0") + end + step 'I should see a discussion has started on commit diff' do page.within(".notes .discussion") do page.should have_content "#{current_user.name} started a discussion on commit" @@ -452,4 +477,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps def have_visible_content (text) have_css("*", text: text, visible: true) end + + def expect_discussion_badge_to_have_counter(value) + page.within(".notes-tab .badge") do + page.should have_content value + end + end end diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index c6a0ae2ba38e06110bce21311a248aaccd74d27c..06e69441894af6513fb00f07359a446ef8705ce7 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -23,7 +23,7 @@ module SharedDiffNote page.within(diff_file_selector) do click_diff_line(sample_commit.line_code) - page.within("form[rel$='#{sample_commit.line_code}']") do + page.within("form[id$='#{sample_commit.line_code}']") do fill_in "note[note]", with: "Typo, please fix" find(".js-comment-button").trigger("click") sleep 0.05 @@ -33,7 +33,7 @@ module SharedDiffNote step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do click_parallel_diff_line(sample_commit.line_code, 'old') - page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do + page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do fill_in "note[note]", with: "Old comment" find(".js-comment-button").trigger("click") end @@ -41,7 +41,7 @@ module SharedDiffNote step 'I leave a diff comment in a parallel view on the right side like "New comment"' do click_parallel_diff_line(sample_commit.line_code, 'new') - page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do + page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do fill_in "note[note]", with: "New comment" find(".js-comment-button").trigger("click") end @@ -51,7 +51,7 @@ module SharedDiffNote page.within(diff_file_selector) do click_diff_line(sample_commit.line_code) - page.within("form[rel$='#{sample_commit.line_code}']") do + page.within("form[id$='#{sample_commit.line_code}']") do fill_in "note[note]", with: "Should fix it :smile:" find('.js-md-preview-button').click end @@ -62,7 +62,7 @@ module SharedDiffNote page.within(diff_file_selector) do click_diff_line(sample_commit.del_line_code) - page.within("form[rel$='#{sample_commit.del_line_code}']") do + page.within("form[id$='#{sample_commit.del_line_code}']") do fill_in "note[note]", with: "DRY this up" find('.js-md-preview-button').click end @@ -91,7 +91,7 @@ module SharedDiffNote page.within(diff_file_selector) do click_diff_line(sample_commit.line_code) - page.within("form[rel$='#{sample_commit.line_code}']") do + page.within("form[id$='#{sample_commit.line_code}']") do fill_in 'note[note]', with: ':smile:' click_button('Add Comment') end diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index bcdfd38d292f245b4fe4df8ed7dc93fb4a67dc57..1a3f662811a0a12467cd8cffe520b38bf64a842d 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -115,6 +115,10 @@ module Ci end if @cache + if @cache[:key] && !validate_string(@cache[:key]) + raise ValidationError, "cache:key parameter should be a string" + end + if @cache[:untracked] && !validate_boolean(@cache[:untracked]) raise ValidationError, "cache:untracked parameter should be an boolean" end @@ -198,6 +202,10 @@ module Ci end def validate_job_cache!(name, job) + if job[:cache][:key] && !validate_string(job[:cache][:key]) + raise ValidationError, "#{name} job: cache:key parameter should be a string" + end + if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked]) raise ValidationError, "#{name} job: cache:untracked parameter should be an boolean" end diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb index 1344f5d120b32f6f17d84dda408ddebb04e00cea..f2020c82d40543d1baf443a66fa0eb44c63dade6 100644 --- a/lib/gitlab/ci/build/artifacts/metadata.rb +++ b/lib/gitlab/ci/build/artifacts/metadata.rb @@ -13,8 +13,8 @@ module Gitlab attr_reader :file, :path, :full_version - def initialize(file, path) - @file, @path = file, path + def initialize(file, path, **opts) + @file, @path, @opts = file, path, opts @full_version = read_version end @@ -52,7 +52,9 @@ module Gitlab def match_entries(gz) entries = {} - match_pattern = %r{^#{Regexp.escape(@path)}[^/]*/?$} + + child_pattern = '[^/]*/?$' unless @opts[:recursive] + match_pattern = /^#{Regexp.escape(@path)}#{child_pattern}/ until gz.eof? do begin diff --git a/lib/gitlab/ci/build/artifacts/metadata/entry.rb b/lib/gitlab/ci/build/artifacts/metadata/entry.rb index 25b71fc3275e00cfd12c60d4bceae22d84e2303b..7f4c750b6fdaf8973296135c86be7a739764bc70 100644 --- a/lib/gitlab/ci/build/artifacts/metadata/entry.rb +++ b/lib/gitlab/ci/build/artifacts/metadata/entry.rb @@ -95,6 +95,13 @@ module Gitlab children.empty? end + def total_size + descendant_pattern = %r{^#{Regexp.escape(@path)}} + entries.sum do |path, entry| + (entry[:size] if path =~ descendant_pattern).to_i + end + end + def to_s @path end diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 79061cd014181d0375b622431ae41c5379f493c6..a484177ae33f4c05a2abd1418211718f76654134 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -1,13 +1,22 @@ module Gitlab module Diff class File - attr_reader :diff + attr_reader :diff, :diff_refs delegate :new_file, :deleted_file, :renamed_file, :old_path, :new_path, to: :diff, prefix: false - def initialize(diff) + def initialize(diff, diff_refs) @diff = diff + @diff_refs = diff_refs + end + + def old_ref + diff_refs[0] if diff_refs + end + + def new_ref + diff_refs[1] if diff_refs end # Array of Gitlab::DIff::Line objects @@ -15,6 +24,14 @@ module Gitlab @lines ||= parser.parse(raw_diff.lines) end + def highlighted_diff_lines + Gitlab::Diff::Highlight.new(self).highlight + end + + def parallel_diff_lines + Gitlab::Diff::ParallelDiff.new(self).parallelize + end + def mode_changed? !!(diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode) end diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb new file mode 100644 index 0000000000000000000000000000000000000000..a7f925ce134f4600ab7b652efb0fc2d8dace4a09 --- /dev/null +++ b/lib/gitlab/diff/highlight.rb @@ -0,0 +1,82 @@ +module Gitlab + module Diff + class Highlight + attr_reader :diff_file, :diff_lines, :raw_lines + + delegate :old_path, :new_path, :old_ref, :new_ref, to: :diff_file, prefix: :diff + + def initialize(diff_lines) + if diff_lines.is_a?(Gitlab::Diff::File) + @diff_file = diff_lines + @diff_lines = @diff_file.diff_lines + else + @diff_lines = diff_lines + end + @raw_lines = @diff_lines.map(&:text) + end + + def highlight + @diff_lines.map.with_index do |diff_line, i| + diff_line = diff_line.dup + # ignore highlighting for "match" lines + next diff_line if diff_line.type == 'match' || diff_line.type == 'nonewline' + + rich_line = highlight_line(diff_line, i) + + if line_inline_diffs = inline_diffs[i] + rich_line = InlineDiffMarker.new(diff_line.text, rich_line).mark(line_inline_diffs) + end + + diff_line.text = rich_line.html_safe + + diff_line + end + end + + private + + def highlight_line(diff_line, index) + return html_escape(diff_line.text) unless diff_file && diff_file.diff_refs + + line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' ' + + case diff_line.type + when 'new', nil + rich_line = new_lines[diff_line.new_pos - 1] + when 'old' + rich_line = old_lines[diff_line.old_pos - 1] + end + + # Only update text if line is found. This will prevent + # issues with submodules given the line only exists in diff content. + rich_line ? line_prefix + rich_line : html_escape(diff_line.text) + end + + def inline_diffs + @inline_diffs ||= InlineDiff.new(@raw_lines).inline_diffs + end + + def old_lines + return unless diff_file + @old_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:old)) + end + + def new_lines + return unless diff_file + @new_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:new)) + end + + def processing_args(version) + ref = send("diff_#{version}_ref") + path = send("diff_#{version}_path") + + [ref.project.repository, ref.id, path] + end + + def html_escape(str) + replacements = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } + str.gsub(/[&"'><]/, replacements) + end + end + end +end diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb new file mode 100644 index 0000000000000000000000000000000000000000..b8a61ad6115a7f830525e612ab3057cbe3ca8d2f --- /dev/null +++ b/lib/gitlab/diff/inline_diff.rb @@ -0,0 +1,73 @@ +module Gitlab + module Diff + class InlineDiff + attr_accessor :lines + + def initialize(lines) + @lines = lines + end + + def inline_diffs + inline_diffs = [] + + local_edit_indexes.each do |index| + old_index = index + new_index = index + 1 + old_line = @lines[old_index] + new_line = @lines[new_index] + + # Skip inline diff if empty line was replaced with content + next if old_line[1..-1] == "" + + # Add one, because this is based on the prefixless version + lcp = longest_common_prefix(old_line[1..-1], new_line[1..-1]) + 1 + lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1]) + + old_diff_range = lcp..(old_line.length - lcs - 1) + new_diff_range = lcp..(new_line.length - lcs - 1) + + inline_diffs[old_index] = [old_diff_range] if old_diff_range.begin <= old_diff_range.end + inline_diffs[new_index] = [new_diff_range] if new_diff_range.begin <= new_diff_range.end + end + + inline_diffs + end + + private + + # Find runs of single line edits + def local_edit_indexes + line_prefixes = @lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' } + joined_line_prefixes = " #{line_prefixes.join} " + + offset = 0 + local_edit_indexes = [] + while index = joined_line_prefixes.index(" -+ ", offset) + local_edit_indexes << index + offset = index + 1 + end + + local_edit_indexes + end + + def longest_common_prefix(a, b) + max_length = [a.length, b.length].max + + length = 0 + (0..max_length - 1).each do |pos| + old_char = a[pos] + new_char = b[pos] + + break if old_char != new_char + length += 1 + end + + length + end + + def longest_common_suffix(a, b) + longest_common_prefix(a.reverse, b.reverse) + end + end + end +end diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb new file mode 100644 index 0000000000000000000000000000000000000000..1d7fa1bce061ec90fea98bf014bb6b185d97cc54 --- /dev/null +++ b/lib/gitlab/diff/inline_diff_marker.rb @@ -0,0 +1,109 @@ +module Gitlab + module Diff + class InlineDiffMarker + attr_accessor :raw_line, :rich_line + + def initialize(raw_line, rich_line = raw_line) + @raw_line = raw_line + @rich_line = rich_line + end + + def mark(line_inline_diffs) + marker_ranges = [] + line_inline_diffs.each do |inline_diff_range| + # Map the inline-diff range based on the raw line to character positions in the rich line + inline_diff_positions = position_mapping[inline_diff_range].flatten + # Turn the array of character positions into ranges + marker_ranges.concat(collapse_ranges(inline_diff_positions)) + end + + offset = 0 + # Mark each range + marker_ranges.each do |range| + offset = insert_around_range(rich_line, range, "<span class='idiff'>", "</span>", offset) + end + + rich_line + end + + private + + # Mapping of character positions in the raw line, to the rich (highlighted) line + def position_mapping + @position_mapping ||= begin + mapping = [] + rich_pos = 0 + (0..raw_line.length).each do |raw_pos| + rich_char = rich_line[rich_pos] + + # The raw and rich lines are the same except for HTML tags, + # so skip over any `<...>` segment + while rich_char == '<' + until rich_char == '>' + rich_pos += 1 + rich_char = rich_line[rich_pos] + end + + rich_pos += 1 + rich_char = rich_line[rich_pos] + end + + # multi-char HTML entities in the rich line correspond to a single character in the raw line + if rich_char == '&' + multichar_mapping = [rich_pos] + until rich_char == ';' + rich_pos += 1 + multichar_mapping << rich_pos + rich_char = rich_line[rich_pos] + end + + mapping[raw_pos] = multichar_mapping + else + mapping[raw_pos] = rich_pos + end + + rich_pos += 1 + end + + mapping + end + end + + # Takes an array of integers, and returns an array of ranges covering the same integers + def collapse_ranges(positions) + return [] if positions.empty? + ranges = [] + + start = prev = positions[0] + range = start..prev + positions[1..-1].each do |pos| + if pos == prev + 1 + range = start..pos + prev = pos + else + ranges << range + start = prev = pos + range = start..prev + end + end + ranges << range + + ranges + end + + # Inserts tags around the characters identified by the given range + def insert_around_range(text, range, before, after, offset = 0) + # Just to be sure + return offset if offset + range.end + 1 > text.length + + text.insert(offset + range.begin, before) + offset += before.length + + text.insert(offset + range.end + 1, after) + offset += after.length + + offset + end + end + end +end diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index 0072194606e69b5c9da9f086c80bd7fa2a630e16..03730b435adc5fdceaf404198ee07ccf9da58ace 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -1,7 +1,8 @@ module Gitlab module Diff class Line - attr_reader :type, :text, :index, :old_pos, :new_pos + attr_reader :type, :index, :old_pos, :new_pos + attr_accessor :text def initialize(text, type, index, old_pos, new_pos) @text, @type, @index = text, type, index diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb new file mode 100644 index 0000000000000000000000000000000000000000..74f9b3c050a616a62cb9a4c1e2bafb0b17734c90 --- /dev/null +++ b/lib/gitlab/diff/parallel_diff.rb @@ -0,0 +1,119 @@ +module Gitlab + module Diff + class ParallelDiff + attr_accessor :diff_file + + def initialize(diff_file) + @diff_file = diff_file + end + + def parallelize + lines = [] + skip_next = false + + highlighted_diff_lines = diff_file.highlighted_diff_lines + highlighted_diff_lines.each do |line| + full_line = line.text + type = line.type + line_code = generate_line_code(diff_file.file_path, line) + line_new = line.new_pos + line_old = line.old_pos + + next_line = diff_file.next_line(line.index) + + if next_line + next_line = highlighted_diff_lines[next_line.index] + next_line_code = generate_line_code(diff_file.file_path, next_line) + next_type = next_line.type + next_line = next_line.text + end + + case type + when 'match', nil + # line in the right panel is the same as in the left one + lines << { + left: { + type: type, + number: line_old, + text: full_line, + line_code: line_code, + }, + right: { + type: type, + number: line_new, + text: full_line, + line_code: line_code + } + } + when 'old' + case next_type + when 'new' + # Left side has text removed, right side has text added + lines << { + left: { + type: type, + number: line_old, + text: full_line, + line_code: line_code, + }, + right: { + type: next_type, + number: line_new, + text: next_line, + line_code: next_line_code + } + } + skip_next = true + when 'old', 'nonewline', nil + # Left side has text removed, right side doesn't have any change + # No next line code, no new line number, no new line text + lines << { + left: { + type: type, + number: line_old, + text: full_line, + line_code: line_code, + }, + right: { + type: next_type, + number: nil, + text: "", + line_code: nil + } + } + end + when 'new' + if skip_next + # Change has been already included in previous line so no need to do it again + skip_next = false + next + else + # Change is only on the right side, left side has no change + lines << { + left: { + type: nil, + number: nil, + text: "", + line_code: line_code, + }, + right: { + type: type, + number: line_new, + text: full_line, + line_code: line_code + } + } + end + end + end + lines + end + + private + + def generate_line_code(file_path, line) + Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) + end + end + end +end diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb index 516e59b87a38293bd87ca0c33fcb1b3c76d4a5f3..3666063bf8b81c25b30de52250f9600fe5afcdf7 100644 --- a/lib/gitlab/diff/parser.rb +++ b/lib/gitlab/diff/parser.rb @@ -11,13 +11,10 @@ module Gitlab line_new = 1 type = nil - lines_arr = ::Gitlab::InlineDiff.processing lines - - lines_arr.each do |line| + @lines.each do |line| next if filename?(line) - full_line = html_escape(line.gsub(/\n/, '')) - full_line = ::Gitlab::InlineDiff.replace_markers full_line + full_line = line.gsub(/\n/, '') if line.match(/^@@ -/) type = "match" @@ -29,6 +26,10 @@ module Gitlab lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) line_obj_index += 1 next + elsif line[0] == '\\' + type = 'nonewline' + lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) + line_obj_index += 1 else type = identification_type(line) lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) @@ -36,10 +37,13 @@ module Gitlab end - if line[0] == "+" + case line[0] + when "+" line_new += 1 - elsif line[0] == "-" + when "-" line_old += 1 + when "\\" + # No increment else line_new += 1 line_old += 1 @@ -62,19 +66,15 @@ module Gitlab end def identification_type(line) - if line[0] == "+" + case line[0] + when "+" "new" - elsif line[0] == "-" + when "-" "old" else nil end end - - def html_escape(str) - replacements = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } - str.gsub(/[&"'><]/, replacements) - end end end end diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 18929b9113b89dbea4e128a13c239544013b5b8e..663402e81975c5d3f2bc735423e50bcfa7025abb 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -82,8 +82,12 @@ module Gitlab end true - rescue Gitlab::Shell::Error - false + rescue Gitlab::Shell::Error => e + if e.message =~ /repository not exported/ + true + else + false + end end end end diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index b7c47958cc7f58a4f6a0a5efb44e770964934e50..f96fed0f5cfb1359418a64bae9ce86d04e1a5b81 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -18,7 +18,7 @@ module Gitlab end def cross_project? - source_repo.fork == true + source_repo.id != target_repo.id end def number @@ -73,6 +73,10 @@ module Gitlab project end + def target_repo + raw_data.base.repo + end + def target_branch target_project.repository.find_branch(raw_data.base.ref) end diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb new file mode 100644 index 0000000000000000000000000000000000000000..4ddb4fea977ccd7276a41fea3e445a02c79b19b5 --- /dev/null +++ b/lib/gitlab/highlight.rb @@ -0,0 +1,38 @@ +module Gitlab + class Highlight + def self.highlight(blob_name, blob_content, nowrap: true) + new(blob_name, blob_content, nowrap: nowrap).highlight(blob_content, continue: false) + end + + def self.highlight_lines(repository, ref, file_name) + blob = repository.blob_at(ref, file_name) + return [] unless blob + + highlight(file_name, blob.data).lines.map!(&:html_safe) + end + + def initialize(blob_name, blob_content, nowrap: true) + @formatter = rouge_formatter(nowrap: nowrap) + @lexer = Rouge::Lexer.guess(filename: blob_name, source: blob_content).new rescue Rouge::Lexers::PlainText + end + + def highlight(text, continue: true) + @formatter.format(@lexer.lex(text, continue: continue)).html_safe + rescue + @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe + end + + private + + def rouge_formatter(options = {}) + options = options.reverse_merge( + nowrap: true, + cssclass: 'code highlight', + lineanchors: true, + lineanchorsid: 'LC' + ) + + Rouge::Formatters::HTMLGitlab.new(options) + end + end +end diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb deleted file mode 100644 index 44507bde25deaef0e8f16adab6ca7af2670c99d7..0000000000000000000000000000000000000000 --- a/lib/gitlab/inline_diff.rb +++ /dev/null @@ -1,104 +0,0 @@ -module Gitlab - class InlineDiff - class << self - - START = "#!idiff-start!#" - FINISH = "#!idiff-finish!#" - - def processing(diff_arr) - indexes = _indexes_of_changed_lines diff_arr - - indexes.each do |index| - first_line = diff_arr[index+1] - second_line = diff_arr[index+2] - - # Skip inline diff if empty line was replaced with content - next if first_line == "-\n" - - first_token = find_first_token(first_line, second_line) - apply_first_token(diff_arr, index, first_token) - - last_token = find_last_token(first_line, second_line, first_token) - apply_last_token(diff_arr, index, last_token) - end - - diff_arr - end - - def apply_first_token(diff_arr, index, first_token) - start = first_token + START - - if first_token.empty? - # In case if we remove string of spaces in commit - diff_arr[index+1].sub!("-", "-" => "-#{START}") - diff_arr[index+2].sub!("+", "+" => "+#{START}") - else - diff_arr[index+1].sub!(first_token, first_token => start) - diff_arr[index+2].sub!(first_token, first_token => start) - end - end - - def apply_last_token(diff_arr, index, last_token) - # This is tricky: escape backslashes so that `sub` doesn't interpret them - # as backreferences. Regexp.escape does NOT do the right thing. - replace_token = FINISH + last_token.gsub(/\\/, '\&\&') - diff_arr[index+1].sub!(/#{Regexp.escape(last_token)}$/, replace_token) - diff_arr[index+2].sub!(/#{Regexp.escape(last_token)}$/, replace_token) - end - - def find_first_token(first_line, second_line) - max_length = [first_line.size, second_line.size].max - first_the_same_symbols = 0 - - (0..max_length + 1).each do |i| - first_the_same_symbols = i - 1 - - if first_line[i] != second_line[i] && i > 0 - break - end - end - - first_line[0..first_the_same_symbols][1..-1] - end - - def find_last_token(first_line, second_line, first_token) - max_length = [first_line.size, second_line.size].max - last_the_same_symbols = 0 - - (1..max_length + 1).each do |i| - last_the_same_symbols = -i - shortest_line = second_line.size > first_line.size ? first_line : second_line - - if (first_line[-i] != second_line[-i]) || "#{first_token}#{START}".size == shortest_line[1..-i].size - break - end - end - - last_the_same_symbols += 1 - first_line[last_the_same_symbols..-1] - end - - def _indexes_of_changed_lines(diff_arr) - chain_of_first_symbols = "" - diff_arr.each_with_index do |line, i| - chain_of_first_symbols += line[0] - end - chain_of_first_symbols.gsub!(/[^\-\+]/, "#") - - offset = 0 - indexes = [] - while index = chain_of_first_symbols.index("#-+#", offset) - indexes << index - offset = index + 1 - end - indexes - end - - def replace_markers(line) - line.gsub!(START, "<span class='idiff'>") - line.gsub!(FINISH, "</span>") - line - end - end - end -end diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb index 938219efdb29d6cf75e2d84dad062e7a267243ec..38364a0b151289a753e1a7a3d610f228fc91f4c4 100644 --- a/lib/gitlab/snippet_search_results.rb +++ b/lib/gitlab/snippet_search_results.rb @@ -1,5 +1,7 @@ module Gitlab class SnippetSearchResults < SearchResults + include SnippetsHelper + attr_reader :limit_snippet_ids def initialize(limit_snippet_ids, query) @@ -47,85 +49,5 @@ module Gitlab def default_scope 'snippet_blobs' end - - # Get an array of line numbers surrounding a matching - # line, bounded by min/max. - # - # @returns Array of line numbers - def bounded_line_numbers(line, min, max) - lower = line - surrounding_lines > min ? line - surrounding_lines : min - upper = line + surrounding_lines < max ? line + surrounding_lines : max - (lower..upper).to_a - end - - # Returns a sorted set of lines to be included in a snippet preview. - # This ensures matching adjacent lines do not display duplicated - # surrounding code. - # - # @returns Array, unique and sorted. - def matching_lines(lined_content) - used_lines = [] - lined_content.each_with_index do |line, line_number| - used_lines.concat bounded_line_numbers( - line_number, - 0, - lined_content.size - ) if line.include?(query) - end - - used_lines.uniq.sort - end - - # 'Chunkify' entire snippet. Splits the snippet data into matching lines + - # surrounding_lines() worth of unmatching lines. - # - # @returns a hash with {snippet_object, snippet_chunks:{data,start_line}} - def chunk_snippet(snippet) - lined_content = snippet.content.split("\n") - used_lines = matching_lines(lined_content) - - snippet_chunk = [] - snippet_chunks = [] - snippet_start_line = 0 - last_line = -1 - - # Go through each used line, and add consecutive lines as a single chunk - # to the snippet chunk array. - used_lines.each do |line_number| - if last_line < 0 - # Start a new chunk. - snippet_start_line = line_number - snippet_chunk << lined_content[line_number] - elsif last_line == line_number - 1 - # Consecutive line, continue chunk. - snippet_chunk << lined_content[line_number] - else - # Non-consecutive line, add chunk to chunk array. - snippet_chunks << { - data: snippet_chunk.join("\n"), - start_line: snippet_start_line + 1 - } - - # Start a new chunk. - snippet_chunk = [lined_content[line_number]] - snippet_start_line = line_number - end - last_line = line_number - end - # Add final chunk to chunk array - snippet_chunks << { - data: snippet_chunk.join("\n"), - start_line: snippet_start_line + 1 - } - - # Return snippet with chunk array - { snippet_object: snippet, snippet_chunks: snippet_chunks } - end - - # Defines how many unmatching lines should be - # included around the matching lines in a snippet - def surrounding_lines - 3 - end end end diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index c5f07c8b508a948796901b0cf6bcc5024360ba71..1633891c8a0749493e2f45175d3cd708a84f9ca7 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -38,6 +38,7 @@ web_server_pid_path="$pid_path/unicorn.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" mail_room_enabled=false mail_room_pid_path="$pid_path/mail_room.pid" +gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse && pwd) gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid" gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public" gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" @@ -233,10 +234,12 @@ start_gitlab() { if [ "$gitlab_workhorse_status" = "0" ]; then echo "The gitlab-workhorse is already running with pid $spid, not restarting" else - # No need to remove a socket, gitlab-workhorse does this itself + # No need to remove a socket, gitlab-workhorse does this itself. + # Because gitlab-workhorse has multiple executables we need to fix + # the PATH. $app_root/bin/daemon_with_pidfile $gitlab_workhorse_pid_path \ - $app_root/../gitlab-workhorse/gitlab-workhorse \ - $gitlab_workhorse_options \ + /usr/bin/env PATH=$gitlab_workhorse_dir:$PATH \ + gitlab-workhorse $gitlab_workhorse_options \ >> $gitlab_workhorse_log 2>&1 & fi diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index 1937ca582b0548939c42d2aca1bab39b45357b2a..4e6e56ac2db5ad3f5e065fd41506bb649bd15a2a 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -30,6 +30,9 @@ web_server_pid_path="$pid_path/unicorn.pid" # The default is "$pid_path/sidekiq.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" +# The directory where the gitlab-workhorse binaries are. Usually +# /home/git/gitlab-workhorse . +gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse && pwd) gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid" # The -listenXxx settings determine where gitlab-workhorse # listens for connections from NGINX. To listen on localhost:8181, write diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index f0fc6916c4d2cdb6039a06ae98ca4f958b9c1e61..1a360cd1ebc821a14b5e562c61bf58dc25bbbff6 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -167,7 +167,7 @@ describe 'Comments', feature: true do end it 'should be removed when canceled' do - page.within(".diff-file form[rel$='#{line_code}']") do + page.within(".diff-file form[id$='#{line_code}']") do find('.js-close-discussion-note-form').trigger('click') end diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml new file mode 100644 index 0000000000000000000000000000000000000000..a326b651aad74cfa9c5a8e46d4845aa2f6167e61 --- /dev/null +++ b/spec/fixtures/parallel_diff_result.yml @@ -0,0 +1,324 @@ +--- +- :left: + :type: match + :number: 6 + :text: "@@ -6,12 +6,18 @@ module Popen" + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 + :right: + :type: match + :number: 6 + :text: "@@ -6,12 +6,18 @@ module Popen" + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 +- :left: + :type: + :number: 6 + :text: |2 + <span id="LC6" class="line"></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 + :right: + :type: + :number: 6 + :text: |2 + <span id="LC6" class="line"></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 +- :left: + :type: + :number: 7 + :text: |2 + <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7 + :right: + :type: + :number: 7 + :text: |2 + <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7 +- :left: + :type: + :number: 8 + :text: |2 + <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8 + :right: + :type: + :number: 8 + :text: |2 + <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8 +- :left: + :type: old + :number: 9 + :text: | + -<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9 + :right: + :type: new + :number: 9 + :text: | + +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff'> </span><span class="s2">"System commands must be given as an array of strings"</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9 +- :left: + :type: + :number: 10 + :text: |2 + <span id="LC10" class="line"> <span class="k">end</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10 + :right: + :type: + :number: 10 + :text: |2 + <span id="LC10" class="line"> <span class="k">end</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10 +- :left: + :type: + :number: 11 + :text: |2 + <span id="LC11" class="line"></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11 + :right: + :type: + :number: 11 + :text: |2 + <span id="LC11" class="line"></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11 +- :left: + :type: + :number: 12 + :text: |2 + <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12 + :right: + :type: + :number: 12 + :text: |2 + <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12 +- :left: + :type: old + :number: 13 + :text: | + -<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13 + :right: + :type: old + :number: + :text: '' + :line_code: +- :left: + :type: old + :number: 14 + :text: | + -<span id="LC14" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13 + :right: + :type: new + :number: 13 + :text: | + +<span id="LC13" class="line"></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13 +- :left: + :type: + :number: + :text: '' + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14 + :right: + :type: new + :number: 14 + :text: | + +<span id="LC14" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14 +- :left: + :type: + :number: + :text: '' + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15 + :right: + :type: new + :number: 15 + :text: | + +<span id="LC15" class="line"> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15 +- :left: + :type: + :number: + :text: '' + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16 + :right: + :type: new + :number: 16 + :text: | + +<span id="LC16" class="line"> <span class="p">}</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16 +- :left: + :type: + :number: + :text: '' + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17 + :right: + :type: new + :number: 17 + :text: | + +<span id="LC17" class="line"></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17 +- :left: + :type: + :number: + :text: '' + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18 + :right: + :type: new + :number: 18 + :text: | + +<span id="LC18" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18 +- :left: + :type: + :number: + :text: '' + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19 + :right: + :type: new + :number: 19 + :text: | + +<span id="LC19" class="line"> <span class="ss">chdir: </span><span class="n">path</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19 +- :left: + :type: + :number: + :text: '' + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20 + :right: + :type: new + :number: 20 + :text: | + +<span id="LC20" class="line"> <span class="p">}</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20 +- :left: + :type: + :number: 15 + :text: |2 + <span id="LC21" class="line"></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21 + :right: + :type: + :number: 21 + :text: |2 + <span id="LC21" class="line"></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21 +- :left: + :type: + :number: 16 + :text: |2 + <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22 + :right: + :type: + :number: 22 + :text: |2 + <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22 +- :left: + :type: + :number: 17 + :text: |2 + <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23 + :right: + :type: + :number: 23 + :text: |2 + <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23 +- :left: + :type: match + :number: 19 + :text: "@@ -19,6 +25,7 @@ module Popen" + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 + :right: + :type: match + :number: 25 + :text: "@@ -19,6 +25,7 @@ module Popen" + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 +- :left: + :type: + :number: 19 + :text: |2 + <span id="LC25" class="line"></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 + :right: + :type: + :number: 25 + :text: |2 + <span id="LC25" class="line"></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 +- :left: + :type: + :number: 20 + :text: |2 + <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 + :right: + :type: + :number: 26 + :text: |2 + <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 +- :left: + :type: + :number: 21 + :text: |2 + <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27 + :right: + :type: + :number: 27 + :text: |2 + <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27 +- :left: + :type: + :number: + :text: '' + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28 + :right: + :type: new + :number: 28 + :text: | + +<span id="LC28" class="line"></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28 +- :left: + :type: + :number: 22 + :text: |2 + <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29 + :right: + :type: + :number: 29 + :text: |2 + <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29 +- :left: + :type: + :number: 23 + :text: |2 + <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30 + :right: + :type: + :number: 30 + :text: |2 + <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30 +- :left: + :type: + :number: 24 + :text: |2 + <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31 + :right: + :type: + :number: 31 + :text: |2 + <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span> + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31 diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index b8bba36439aecd580b94471d884e7b5e412094bd..87849230dbe97b883591d92e7330ed486a82d239 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -1,22 +1,22 @@ require 'spec_helper' describe BlobHelper do - describe 'highlight' do - let(:blob_name) { 'test.lisp' } - let(:no_context_content) { ":type \"assem\"))" } - let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" } - let(:split_content) { blob_content.split("\n") } - let(:multiline_content) do - %q( - def test(input): - """This is line 1 of a multi-line comment. - This is line 2. - """ - ) - end + let(:blob_name) { 'test.lisp' } + let(:no_context_content) { ":type \"assem\"))" } + let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" } + let(:split_content) { blob_content.split("\n") } + let(:multiline_content) do + %q( + def test(input): + """This is line 1 of a multi-line comment. + This is line 2. + """ + ) + end + describe '#highlight' do it 'should return plaintext for unknown lexer context' do - result = highlight(blob_name, no_context_content, nowrap: true, continue: false) + result = helper.highlight(blob_name, no_context_content, nowrap: true) expect(result).to eq('<span id="LC1" class="line">:type "assem"))</span>') end @@ -24,28 +24,17 @@ describe BlobHelper do expected = %Q[<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span> <span id="LC2" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span>] - expect(highlight(blob_name, blob_content, nowrap: true, continue: false)).to eq(expected) - end - - it 'should highlight continued blocks' do - # Both lines have LC1 as ID since formatter doesn't support continue at the moment - expected = [ - '<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>', - '<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span>' - ] - - result = split_content.map{ |content| highlight(blob_name, content, nowrap: true, continue: true) } - expect(result).to eq(expected) + expect(helper.highlight(blob_name, blob_content, nowrap: true)).to eq(expected) end it 'should highlight multi-line comments' do - result = highlight(blob_name, multiline_content, nowrap: true, continue: false) + result = helper.highlight(blob_name, multiline_content, nowrap: true) html = Nokogiri::HTML(result) lines = html.search('.s') expect(lines.count).to eq(3) expect(lines[0].text).to eq('"""This is line 1 of a multi-line comment.') - expect(lines[1].text).to eq(' This is line 2.') - expect(lines[2].text).to eq(' """') + expect(lines[1].text).to eq(' This is line 2.') + expect(lines[2].text).to eq(' """') end context 'diff highlighting' do @@ -59,9 +48,23 @@ describe BlobHelper do end it 'should highlight each line properly' do - result = highlight(blob_name, blob_content, nowrap: true, continue: false) + result = helper.highlight(blob_name, blob_content, nowrap: true) expect(result).to eq(expected) end end end + + describe "#highlighter" do + it 'should highlight continued blocks' do + # Both lines have LC1 as ID since formatter doesn't support continue at the moment + expected = [ + '<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>', + '<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span>' + ] + + highlighter = helper.highlighter(blob_name, blob_content, nowrap: true) + result = split_content.map{ |content| highlighter.highlight(content) } + expect(result).to eq(expected) + end + end end diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 7c96a74e5816c0329a4094f82cd63ee95f40f3fb..955d2852cfddfa9f34195e4badf5fa86580aefa7 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -4,10 +4,12 @@ describe DiffHelper do include RepoHelpers let(:project) { create(:project) } + let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } let(:diffs) { commit.diffs } let(:diff) { diffs.first } - let(:diff_file) { Gitlab::Diff::File.new(diff) } + let(:diff_refs) { [commit.parent, commit] } + let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) } describe 'diff_hard_limit_enabled?' do it 'should return true if param is provided' do @@ -44,55 +46,41 @@ describe DiffHelper do describe 'safe_diff_files' do it 'should return all files from a commit that is smaller than safe limits' do - expect(safe_diff_files(diffs).length).to eq(2) + expect(safe_diff_files(diffs, diff_refs).length).to eq(2) end it 'should return only the first file if the diff line count in the 2nd file takes the total beyond safe limits' do allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines - expect(safe_diff_files(diffs).length).to eq(1) + expect(safe_diff_files(diffs, diff_refs).length).to eq(1) end it 'should return all files from a commit that is beyond safe limit for numbers of lines if force diff is true' do allow(controller).to receive(:params) { { force_show_diff: true } } allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines - expect(safe_diff_files(diffs).length).to eq(2) + expect(safe_diff_files(diffs, diff_refs).length).to eq(2) end it 'should return only the first file if the diff line count in the 2nd file takes the total beyond hard limits' do allow(controller).to receive(:params) { { force_show_diff: true } } allow(diffs[1].diff).to receive(:lines).and_return([""] * 49999) #simulate 49999 lines - expect(safe_diff_files(diffs).length).to eq(1) + expect(safe_diff_files(diffs, diff_refs).length).to eq(1) end it 'should return only a safe number of file diffs if a commit touches more files than the safe limits' do large_diffs = diffs * 100 #simulate 200 diffs - expect(safe_diff_files(large_diffs).length).to eq(100) + expect(safe_diff_files(large_diffs, diff_refs).length).to eq(100) end it 'should return all file diffs if a commit touches more files than the safe limits but force diff is true' do allow(controller).to receive(:params) { { force_show_diff: true } } large_diffs = diffs * 100 #simulate 200 diffs - expect(safe_diff_files(large_diffs).length).to eq(200) + expect(safe_diff_files(large_diffs, diff_refs).length).to eq(200) end it 'should return a limited file diffs if a commit touches more files than the hard limits and force diff is true' do allow(controller).to receive(:params) { { force_show_diff: true } } very_large_diffs = diffs * 1000 #simulate 2000 diffs - expect(safe_diff_files(very_large_diffs).length).to eq(1000) - end - end - - describe 'parallel_diff' do - it 'should return an array of arrays containing the parsed diff' do - expect(parallel_diff(diff_file, 0)). - to match_array(parallel_diff_result_array) - end - end - - describe 'generate_line_code' do - it 'should generate correct line code' do - expect(generate_line_code(diff_file.file_path, diff_file.diff_lines.first)). - to eq('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6') + expect(safe_diff_files(very_large_diffs, diff_refs).length).to eq(1000) end end @@ -126,39 +114,11 @@ describe DiffHelper do expect(diff_line_content(diff_file.diff_lines.first.text)). to eq('@@ -6,12 +6,18 @@ module Popen') expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match') - expect(diff_line_content(diff_file.diff_lines.first.new_pos)).to eq(6) + expect(diff_file.diff_lines.first.new_pos).to eq(6) end - end - def parallel_diff_result_array - [ - ["match", 6, "@@ -6,12 +6,18 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6", "match", 6, "@@ -6,12 +6,18 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6"], - [nil, 6, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6", nil, 6, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6"], [nil, 7, " def popen(cmd, path=nil)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7", nil, 7, " def popen(cmd, path=nil)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"], - [nil, 8, " unless cmd.is_a?(Array)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8", nil, 8, " unless cmd.is_a?(Array)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], - ["old", 9, "- raise <span class='idiff'></span>"System commands must be given as an array of strings"", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9", "new", 9, "+ raise <span class='idiff'>RuntimeError, </span>"System commands must be given as an array of strings"", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], - [nil, 10, " end", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10", nil, 10, " end", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"], - [nil, 11, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11", nil, 11, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11"], - [nil, 12, " path ||= Dir.pwd", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12", nil, 12, " path ||= Dir.pwd", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12"], - ["old", 13, "- vars = { "PWD" => path }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13", "old", nil, " ", nil], - ["old", 14, "- options = { chdir: path }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13", "new", 13, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13"], - [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14", "new", 14, "+ vars = {", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14"], - [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15", "new", 15, "+ "PWD" => path", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"], - [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16", "new", 16, "+ }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16"], - [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17", "new", 17, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17"], - [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18", "new", 18, "+ options = {", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18"], - [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19", "new", 19, "+ chdir: path", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19"], - [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20", "new", 20, "+ }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20"], - [nil, 15, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21", nil, 21, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21"], - [nil, 16, " unless File.directory?(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22", nil, 22, " unless File.directory?(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22"], - [nil, 17, " FileUtils.mkdir_p(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23", nil, 23, " FileUtils.mkdir_p(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23"], - ["match", 19, "@@ -19,6 +25,7 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25", "match", 25, "@@ -19,6 +25,7 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25"], - [nil, 19, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25", nil, 25, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25"], - [nil, 20, " @cmd_output = """, "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26", nil, 26, " @cmd_output = """, "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26"], - [nil, 21, " @cmd_status = 0", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27", nil, 27, " @cmd_status = 0", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27"], - [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28", "new", 28, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28"], - [nil, 22, " Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29", nil, 29, " Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29"], - [nil, 23, " @cmd_output << stdout.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30", nil, 30, " @cmd_output << stdout.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30"], - [nil, 24, " @cmd_output << stderr.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31", nil, 31, " @cmd_output << stderr.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31"] - ] + it 'should return safe HTML' do + expect(diff_line_content(diff_file.diff_lines.first.text)).to be_html_safe + end end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index d15100fc6d8c075de90677c6129d4bb0d24c8fcc..f3394910c5b34d63e0a2fd6dd4b552826dbeac65 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -336,7 +336,7 @@ module Ci describe "Caches" do it "returns cache when defined globally" do config = YAML.dump({ - cache: { paths: ["logs/", "binaries/"], untracked: true }, + cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' }, rspec: { script: "rspec" } @@ -348,13 +348,14 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( paths: ["logs/", "binaries/"], untracked: true, + key: 'key', ) end it "returns cache when defined in a job" do config = YAML.dump({ rspec: { - cache: { paths: ["logs/", "binaries/"], untracked: true }, + cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' }, script: "rspec" } }) @@ -365,15 +366,16 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( paths: ["logs/", "binaries/"], untracked: true, + key: 'key', ) end it "overwrite cache when defined for a job and globally" do config = YAML.dump({ - cache: { paths: ["logs/", "binaries/"], untracked: true }, + cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' }, rspec: { script: "rspec", - cache: { paths: ["test/"], untracked: false }, + cache: { paths: ["test/"], untracked: false, key: 'local' }, } }) @@ -383,6 +385,7 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( paths: ["test/"], untracked: false, + key: 'local', ) end end @@ -615,6 +618,20 @@ module Ci end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths parameter should be an array of strings") end + it "returns errors if cache:key is not a string" do + config = YAML.dump({ cache: { key: 1 }, rspec: { script: "test" } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:key parameter should be a string") + end + + it "returns errors if job cache:key is not an a string" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { key: 1 } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:key parameter should be a string") + end + it "returns errors if job cache:untracked is not an array of strings" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } }) expect do diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb index 41257103eadbae0377580ab449a1547b18923c52..acca0b08bab3497622a089a26cd9ee614f59a682 100644 --- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb @@ -4,13 +4,13 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do let(:entries) do { 'path/' => {}, 'path/dir_1/' => {}, - 'path/dir_1/file_1' => {}, - 'path/dir_1/file_b' => {}, + 'path/dir_1/file_1' => { size: 10 }, + 'path/dir_1/file_b' => { size: 10 }, 'path/dir_1/subdir/' => {}, - 'path/dir_1/subdir/subfile' => {}, + 'path/dir_1/subdir/subfile' => { size: 10 }, 'path/second_dir' => {}, - 'path/second_dir/dir_3/file_2' => {}, - 'path/second_dir/dir_3/file_3'=> {}, + 'path/second_dir/dir_3/file_2' => { size: 10 }, + 'path/second_dir/dir_3/file_3'=> { size: 10 }, 'another_directory/'=> {}, 'another_file' => {}, '/file/with/absolute_path' => {} } @@ -112,6 +112,11 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do subject { |example| path(example).empty? } it { is_expected.to be false } end + + describe '#total_size' do + subject { |example| path(example).total_size } + it { is_expected.to eq(30) } + end end end diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb index 828eedfa7b03c923c8faafd2656c4818424d2568..eea01f91879ec97d2ded8e56b17119ff4b74e6c5 100644 --- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Build::Artifacts::Metadata do - def metadata(path = '') - described_class.new(metadata_file_path, path) + def metadata(path = '', **opts) + described_class.new(metadata_file_path, path, **opts) end let(:metadata_file_path) do @@ -51,6 +51,19 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do end end + describe '#find_entries! recursively for other_artifacts_0.1.2/' do + subject { metadata('other_artifacts_0.1.2/', recursive: true).find_entries! } + + it 'matches correct paths' do + expect(subject.keys). + to contain_exactly 'other_artifacts_0.1.2/', + 'other_artifacts_0.1.2/doc_sample.txt', + 'other_artifacts_0.1.2/another-subdirectory/', + 'other_artifacts_0.1.2/another-subdirectory/empty_directory/', + 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' + end + end + describe '#to_entry' do subject { metadata('').to_entry } it { is_expected.to be_an_instance_of(Gitlab::Ci::Build::Artifacts::Metadata::Entry) } diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index c7cdf8691d61b37217bb47dd61439aa47dcb101c..0d9694f2c1304d44023ac55ee33bc3e11d79282e 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Diff::File, lib: true do let(:project) { create(:project) } let(:commit) { project.commit(sample_commit.id) } let(:diff) { commit.diffs.first } - let(:diff_file) { Gitlab::Diff::File.new(diff) } + let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) } describe :diff_lines do let(:diff_lines) { diff_file.diff_lines } diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b84a57f357adaf15a5d381716da4777bff698c2f --- /dev/null +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Gitlab::Diff::Highlight, lib: true do + include RepoHelpers + + let(:project) { create(:project) } + let(:commit) { project.commit(sample_commit.id) } + let(:diff) { commit.diffs.first } + let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) } + + describe '#highlight' do + let(:diff_lines) { Gitlab::Diff::Highlight.new(diff_file).highlight } + + it 'should return Gitlab::Diff::Line elements' do + expect(diff_lines.first).to be_an_instance_of(Gitlab::Diff::Line) + end + + it 'should not modify "match" lines' do + expect(diff_lines[0].text).to eq('@@ -6,12 +6,18 @@ module Popen') + expect(diff_lines[22].text).to eq('@@ -19,6 +25,7 @@ module Popen') + end + + it 'should highlight unchanged lines' do + code = %Q{ <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n} + + expect(diff_lines[2].text).to eq(code) + end + + it 'should highlight removed lines' do + code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n} + + expect(diff_lines[4].text).to eq(code) + end + + it 'should highlight added lines' do + code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n} + + expect(diff_lines[5].text).to eq(code) + end + end +end diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..6f3276a8b533364b367ae2e709c63b52c144d7dc --- /dev/null +++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Gitlab::Diff::InlineDiffMarker, lib: true do + describe '#inline_diffs' do + let(:raw) { "abc 'def'" } + let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">'def'</span>} } + let(:inline_diffs) { [2..5] } + + let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) } + + it 'marks the inline diffs' do + expect(subject).to eq(%{<span class="abc">ab<span class='idiff'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff'>'d</span>ef'</span>}) + end + end +end diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..056917df8933db1f60b341f6b4794716ba0e36da --- /dev/null +++ b/spec/lib/gitlab/diff/inline_diff_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Gitlab::Diff::InlineDiff, lib: true do + describe '#inline_diffs' do + let(:diff) do + <<eos + class Test +- def initialize(test = true) ++ def initialize(test = false) + @test = test + end + end +eos + end + + let(:subject) { Gitlab::Diff::InlineDiff.new(diff.lines).inline_diffs } + + it 'finds all inline diffs' do + expect(subject[0]).to be_nil + expect(subject[1]).to eq([25..27]) + expect(subject[2]).to eq([25..28]) + expect(subject[3]).to be_nil + expect(subject[4]).to be_nil + expect(subject[5]).to be_nil + end + end +end diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..1c5bbc47120477528e573b0d3530bafed7866bb4 --- /dev/null +++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe Gitlab::Diff::ParallelDiff, lib: true do + include RepoHelpers + + let(:project) { create(:project) } + let(:repository) { project.repository } + let(:commit) { project.commit(sample_commit.id) } + let(:diffs) { commit.diffs } + let(:diff) { diffs.first } + let(:diff_refs) { [commit.parent, commit] } + let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) } + subject { described_class.new(diff_file) } + + let(:parallel_diff_result_array) { YAML.load_file("#{Rails.root}/spec/fixtures/parallel_diff_result.yml") } + + describe '#parallelize' do + it 'should return an array of arrays containing the parsed diff' do + expect(subject.parallelize).to match_array(parallel_diff_result_array) + end + end +end diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb index ba577bd28e5fa4bb1d0381376bc0a46a55341b3e..fe0dea77909ae6c3ed97c956e7010905fdfd2ceb 100644 --- a/spec/lib/gitlab/diff/parser_spec.rb +++ b/spec/lib/gitlab/diff/parser_spec.rb @@ -86,7 +86,7 @@ eos it { expect(line.type).to eq(nil) } it { expect(line.old_pos).to eq(24) } it { expect(line.new_pos).to eq(31) } - it { expect(line.text).to eq(' @cmd_output << stderr.read') } + it { expect(line.text).to eq(' @cmd_output << stderr.read') } end end end diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index 9aefec77f6d53882b25467ad7ce3ae95c03d3ca5..6cebcb5009ad99728a9eafa199402e215eaf4977 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -2,8 +2,11 @@ require 'spec_helper' describe Gitlab::GithubImport::PullRequestFormatter, lib: true do let(:project) { create(:project) } - let(:source_branch) { OpenStruct.new(ref: 'feature') } - let(:target_branch) { OpenStruct.new(ref: 'master') } + let(:repository) { OpenStruct.new(id: 1, fork: false) } + let(:source_repo) { repository } + let(:source_branch) { OpenStruct.new(ref: 'feature', repo: source_repo) } + let(:target_repo) { repository } + let(:target_branch) { OpenStruct.new(ref: 'master', repo: target_repo) } let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } @@ -125,10 +128,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do end describe '#cross_project?' do - context 'when source repo is not a fork' do - let(:local_repo) { OpenStruct.new(fork: false) } - let(:source_branch) { OpenStruct.new(ref: 'feature', repo: local_repo) } - let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch)) } + context 'when source, and target repositories are the same' do + let(:raw_data) { OpenStruct.new(base_data) } it 'returns false' do expect(pull_request.cross_project?).to eq false @@ -136,9 +137,17 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do end context 'when source repo is a fork' do - let(:forked_repo) { OpenStruct.new(fork: true) } - let(:source_branch) { OpenStruct.new(ref: 'feature', repo: forked_repo) } - let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch)) } + let(:source_repo) { OpenStruct.new(id: 2, fork: true) } + let(:raw_data) { OpenStruct.new(base_data) } + + it 'returns true' do + expect(pull_request.cross_project?).to eq true + end + end + + context 'when target repo is a fork' do + let(:target_repo) { OpenStruct.new(id: 2, fork: true) } + let(:raw_data) { OpenStruct.new(base_data) } it 'returns true' do expect(pull_request.cross_project?).to eq true diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..1620eb6c60adb507920d56cd7cbf47b8828961a5 --- /dev/null +++ b/spec/lib/gitlab/highlight_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::Highlight, lib: true do + include RepoHelpers + + let(:project) { create(:project) } + let(:commit) { project.commit(sample_commit.id) } + + describe '.highlight_lines' do + let(:lines) do + Gitlab::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb') + end + + it 'should properly highlight all the lines' do + expect(lines[4]).to eq(%Q{<span id="LC5" class="line"> <span class="kp">extend</span> <span class="nb">self</span></span>\n}) + expect(lines[21]).to eq(%Q{<span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n}) + expect(lines[26]).to eq(%Q{<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n}) + end + end + +end diff --git a/spec/lib/gitlab/inline_diff_spec.rb b/spec/lib/gitlab/inline_diff_spec.rb deleted file mode 100644 index c690c195112fd8e7fa173c060b3858c24134a0fa..0000000000000000000000000000000000000000 --- a/spec/lib/gitlab/inline_diff_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'spec_helper' - -describe Gitlab::InlineDiff, lib: true do - describe '#processing' do - let(:diff) do - <<eos ---- a/test.rb -+++ b/test.rb -@@ -1,6 +1,6 @@ - class Test - def cleanup_string(input) - return nil if input.nil? -- input.sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip -+ input.to_s.sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip - end - end -eos - end - - let(:expected) do - ["--- a/test.rb\n", - "+++ b/test.rb\n", - "@@ -1,6 +1,6 @@\n", - " class Test\n", - " def cleanup_string(input)\n", - " return nil if input.nil?\n", - "- input.#!idiff-start!##!idiff-finish!#sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip\n", - "+ input.#!idiff-start!#to_s.#!idiff-finish!#sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip\n", - " end\n", - " end\n"] - end - - let(:subject) { Gitlab::InlineDiff.processing(diff.lines) } - - it 'should retain backslashes' do - expect(subject).to eq(expected) - end - end -end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index d12b9e65c8239a4ba910a425ba1ccc902f502d24..d30bc7d05541e02c9c3f39dbe48b9fe4bb0ba5a4 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -362,12 +362,12 @@ describe Ci::Build, models: true do subject { build.artifacts_browse_url } it "should be nil if artifacts browser is unsupported" do - allow(build).to receive(:artifacts_browser_supported?).and_return(false) + allow(build).to receive(:artifacts_metadata?).and_return(false) is_expected.to be_nil end it 'should not be nil if artifacts browser is supported' do - allow(build).to receive(:artifacts_browser_supported?).and_return(true) + allow(build).to receive(:artifacts_metadata?).and_return(true) is_expected.to_not be_nil end end @@ -391,8 +391,8 @@ describe Ci::Build, models: true do end - describe :artifacts_browser_supported? do - subject { build.artifacts_browser_supported? } + describe :artifacts_metadata? do + subject { build.artifacts_metadata? } context 'artifacts metadata does not exist' do it { is_expected.to be_falsy } end diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 449cecaa7896803fc65055a491f41ff8980d61ce..de9fed2b7dd590f662177bf8a915f98a3cdee0a6 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -6,7 +6,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do let(:mr_merge_if_green_enabled) do create(:merge_request, merge_when_build_succeeds: true, merge_user: user, - source_branch: "source_branch", target_branch: project.default_branch, + source_branch: "master", target_branch: 'feature', source_project: project, target_project: project, state: "opened") end