summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-06-22 18:00:11 +0200
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-06-22 18:00:11 +0200
commit73e003013f1f6f81f3ca3518784d41d608490403 (patch)
tree44ea8d05e155938f265fd577ec3d57a4c7e45feb /app
parent3fe3cbf222a036d4487b9630e2abfc58ec7515cf (diff)
parentcc9b5c49d1de2a9e1e90895700376793b9d614f6 (diff)
downloadgitlab-ce-73e003013f1f6f81f3ca3518784d41d608490403.tar.gz
Merge branch 'master' into admin-edit-identities
Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> Conflicts: app/views/admin/users/show.html.haml
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/favicon.icobin32988 -> 5430 bytes
-rw-r--r--app/assets/images/logo-white.pngbin7699 -> 0 bytes
-rw-r--r--app/assets/images/logo.svg26
-rw-r--r--app/assets/images/logo_wordmark.svg26
-rw-r--r--app/assets/javascripts/application.js.coffee3
-rw-r--r--app/assets/javascripts/blob/blob.js.coffee73
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee2
-rw-r--r--app/assets/javascripts/line_highlighter.js.coffee148
-rw-r--r--app/assets/javascripts/merge_request.js.coffee95
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee153
-rw-r--r--app/assets/stylesheets/generic/header.scss4
-rw-r--r--app/controllers/dashboard_controller.rb4
-rw-r--r--app/controllers/passwords_controller.rb2
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb5
-rw-r--r--app/controllers/sessions_controller.rb2
-rw-r--r--app/finders/issuable_finder.rb16
-rw-r--r--app/helpers/appearances_helper.rb2
-rw-r--r--app/helpers/application_helper.rb73
-rw-r--r--app/helpers/broadcast_messages_helper.rb15
-rw-r--r--app/helpers/icons_helper.rb2
-rw-r--r--app/helpers/issues_helper.rb4
-rw-r--r--app/helpers/notes_helper.rb4
-rw-r--r--app/helpers/notifications_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/models/ability.rb2
-rw-r--r--app/models/concerns/mentionable.rb2
-rw-r--r--app/models/merge_request.rb18
-rw-r--r--app/models/milestone.rb2
-rw-r--r--app/models/note.rb5
-rw-r--r--app/models/repository.rb7
-rw-r--r--app/models/snippet.rb1
-rw-r--r--app/models/user.rb43
-rw-r--r--app/services/git_push_service.rb2
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/services/notes/update_service.rb2
-rw-r--r--app/views/admin/users/show.html.haml8
-rw-r--r--app/views/layouts/header/_empty.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml2
-rw-r--r--app/views/projects/_issuable_form.html.haml13
-rw-r--r--app/views/projects/commits/_commit.html.haml2
-rw-r--r--app/views/projects/issues/_issue.html.haml4
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml8
-rw-r--r--app/views/projects/merge_requests/_show.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml7
-rw-r--r--app/views/projects/merge_requests/widget/_closed.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml4
-rw-r--r--app/views/projects/notes/discussions/_active.html.haml4
-rw-r--r--app/views/projects/notes/discussions/_commit.html.haml2
-rw-r--r--app/views/projects/notes/discussions/_outdated.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml4
-rw-r--r--app/views/shared/_file_highlight.html.haml6
-rw-r--r--app/views/shared/_issuable_filter.html.haml6
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rw-r--r--app/views/users/show.html.haml3
56 files changed, 539 insertions, 301 deletions
diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico
index bfb74960c48..3479cbbb46f 100644
--- a/app/assets/images/favicon.ico
+++ b/app/assets/images/favicon.ico
Binary files differ
diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png
deleted file mode 100644
index 917bcfcb7e7..00000000000
--- a/app/assets/images/logo-white.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg
new file mode 100644
index 00000000000..c09785cb96f
--- /dev/null
+++ b/app/assets/images/logo.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="210px" height="210px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+ <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
+ <title>Slice 1</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+ <g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)">
+ <g id="Page-1" sketch:type="MSShapeGroup">
+ <g id="Fill-1-+-Group-24">
+ <g id="Group-24">
+ <g id="Group">
+ <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329"></path>
+ <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26"></path>
+ <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326"></path>
+ <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329"></path>
+ <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26"></path>
+ <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326"></path>
+ <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/assets/images/logo_wordmark.svg b/app/assets/images/logo_wordmark.svg
new file mode 100644
index 00000000000..a37fe1235cb
--- /dev/null
+++ b/app/assets/images/logo_wordmark.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="546px" height="194px" viewBox="0 0 546 194" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+ <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
+ <title>Fill 1 + Group 24</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+ <g id="Fill-1-+-Group-24" sketch:type="MSLayerGroup">
+ <g id="Group-24" sketch:type="MSShapeGroup">
+ <path d="M316.7906,65.3001 C301.5016,65.3001 292.0046,77.4461 292.0046,97.0001 C292.0046,116.5541 301.5016,128.7001 316.7906,128.7001 C322.5346,128.7001 327.8716,127.0711 332.2226,123.9881 L332.4336,123.8391 L332.4336,101.8711 L310.4336,101.8711 L310.4336,94.0711 L341.4336,94.0711 L341.4336,126.8061 C334.8706,133.1501 326.3546,136.5001 316.7906,136.5001 C296.2666,136.5001 283.0046,120.9951 283.0046,97.0001 C283.0046,73.0051 296.2666,57.5001 316.7906,57.5001 C326.7826,57.5001 335.2176,61.1481 341.2206,68.0561 L335.2246,73.0381 C330.6986,67.9041 324.4986,65.3001 316.7906,65.3001 L316.7906,65.3001 Z M489.8836,135.2501 L482.9356,135.2501 L480.6016,128.8021 L480.0486,129.2991 C479.9716,129.3681 472.2196,136.2501 462.4606,136.2501 C452.6096,136.2501 445.4606,129.6961 445.4606,120.6671 C445.4606,107.5951 456.7446,104.8511 466.2096,104.8511 C473.5836,104.8511 480.1886,106.5111 480.2546,106.5281 L480.8776,106.6871 L480.8776,105.1011 C480.8776,97.9861 476.4356,94.3781 467.6726,94.3781 C462.3646,94.3781 456.7556,95.6891 451.4236,98.1701 L447.8206,91.9581 C452.5266,88.8961 459.6726,85.3781 467.6726,85.3781 C481.5806,85.3781 489.8836,92.9341 489.8836,105.5891 L489.8836,135.2501 Z M470.6886,111.7771 C460.0716,111.7771 454.4606,114.8511 454.4606,120.6671 C454.4606,124.7281 457.5256,127.2501 462.4606,127.2501 C470.5906,127.2501 477.7276,123.9181 480.6626,121.9481 L480.8836,121.8001 L480.8836,112.6201 L480.4676,112.5491 C480.4226,112.5411 475.8766,111.7771 470.6886,111.7771 L470.6886,111.7771 Z M440.4576,127.4501 L440.4576,135.2501 L410.4606,135.2501 L410.4606,61.2501 L419.4606,61.2501 L419.4606,127.4501 L440.4576,127.4501 Z M520.9416,136.5001 C515.0966,136.5001 508.6886,135.6961 501.8926,134.1091 L501.8926,61.2501 L510.8926,61.2501 L510.8926,89.3131 L511.6656,88.8111 C511.7146,88.7791 516.7346,85.5711 523.6536,85.5711 C525.0336,85.5711 526.4146,85.7001 527.7486,85.9521 C539.0936,88.2761 545.8666,97.4301 545.8666,110.4391 C545.8666,125.7831 535.6176,136.5001 520.9416,136.5001 L520.9416,136.5001 Z M521.9426,94.3781 C518.3636,94.3781 514.6196,95.6031 511.1166,97.9191 L510.8926,98.0681 L510.8926,127.9021 L511.3196,127.9651 C514.6986,128.4601 517.9356,128.7121 520.9416,128.7121 C530.3176,128.7121 536.8666,121.1971 536.8666,110.4391 C536.8666,100.2321 531.4266,94.3781 521.9426,94.3781 L521.9426,94.3781 Z M398.4516,86.2501 L398.4516,94.0501 L383.4516,94.0501 L383.4516,116.9501 C383.4516,119.7551 384.5436,122.3921 386.5276,124.3741 C388.5096,126.3581 391.1466,127.4501 393.9516,127.4501 L398.4516,127.4501 L398.4516,135.2501 L393.9516,135.2501 C383.1996,135.2501 374.4516,126.5021 374.4516,115.7501 L374.4516,61.2501 L383.4516,61.2501 L383.4516,86.2501 L398.4516,86.2501 Z M353.4426,66.2501 L362.4426,66.2501 L362.4426,75.2501 L353.4426,75.2501 L353.4426,66.2501 Z M353.4426,86.2501 L362.4426,86.2501 L362.4426,135.2501 L353.4426,135.2501 L353.4426,86.2501 Z" id="Fill-2" fill="#8C929D"></path>
+ <g id="Group">
+ <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329"></path>
+ <path id="Fill-6" fill="#FC6D26"></path>
+ <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26"></path>
+ <path id="Fill-10" fill="#FC6D26"></path>
+ <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326"></path>
+ <path id="Fill-14" fill="#FC6D26"></path>
+ <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329"></path>
+ <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26"></path>
+ <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326"></path>
+ <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 6a3f7386d5b..c18ea929506 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -141,8 +141,7 @@ $ ->
$('.trigger-submit').on 'change', ->
$(@).parents('form').submit()
- $("abbr.timeago").timeago()
- $('.js-timeago').timeago()
+ $('abbr.timeago, .js-timeago').timeago()
# Flash
if (flash = $(".flash-container")).length > 0
diff --git a/app/assets/javascripts/blob/blob.js.coffee b/app/assets/javascripts/blob/blob.js.coffee
deleted file mode 100644
index 37a175fdbc7..00000000000
--- a/app/assets/javascripts/blob/blob.js.coffee
+++ /dev/null
@@ -1,73 +0,0 @@
-class @BlobView
- constructor: ->
- # handle multi-line select
- handleMultiSelect = (e) ->
- [ first_line, last_line ] = parseSelectedLines()
- [ line_number ] = parseSelectedLines($(this).attr("id"))
- hash = "L#{line_number}"
-
- if e.shiftKey and not isNaN(first_line) and not isNaN(line_number)
- if line_number < first_line
- last_line = first_line
- first_line = line_number
- else
- last_line = line_number
-
- hash = if first_line == last_line then "L#{first_line}" else "L#{first_line}-#{last_line}"
-
- setHash(hash)
- e.preventDefault()
-
- # See if there are lines selected
- # "#L12" and "#L34-56" supported
- highlightBlobLines = (e) ->
- [ first_line, last_line ] = parseSelectedLines()
-
- unless isNaN first_line
- $("#tree-content-holder .highlight .line").removeClass("hll")
- $("#LC#{line}").addClass("hll") for line in [first_line..last_line]
- $.scrollTo("#L#{first_line}", offset: -50) unless e?
-
- # parse selected lines from hash
- # always return first and last line (initialized to NaN)
- parseSelectedLines = (str) ->
- first_line = NaN
- last_line = NaN
- hash = str || window.location.hash
-
- if hash isnt ""
- matches = hash.match(/\#?L(\d+)(\-(\d+))?/)
- first_line = parseInt(matches?[1])
- last_line = parseInt(matches?[3])
- last_line = first_line if isNaN(last_line)
-
- [ first_line, last_line ]
-
- setHash = (hash) ->
- hash = hash.replace(/^\#/, "")
- nodes = $("#" + hash)
- # if any nodes are using this id, they must be temporarily changed
- # also, add a temporary div at the top of the screen to prevent scrolling
- if nodes.length > 0
- scroll_top = $(document).scrollTop()
- nodes.attr("id", "")
- tmp = $("<div></div>")
- .css({ position: "absolute", visibility: "hidden", top: scroll_top + "px" })
- .attr("id", hash)
- .appendTo(document.body)
-
- window.location.hash = hash
-
- # restore the nodes
- if nodes.length > 0
- tmp.remove()
- nodes.attr("id", hash)
-
- # initialize multi-line select
- $("#tree-content-holder .line-numbers a[id^=L]").on("click", handleMultiSelect)
-
- # Highlight the correct lines on load
- highlightBlobLines()
-
- # Highlight the correct lines when the hash part of the URL changes
- $(window).on("hashchange", highlightBlobLines)
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index b7ebe6a5c89..84873e389ea 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -87,7 +87,7 @@ class Dispatcher
new TreeView()
shortcut_handler = new ShortcutsNavigation()
when 'projects:blob:show'
- new BlobView()
+ new LineHighlighter()
shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit'
new Labels()
diff --git a/app/assets/javascripts/line_highlighter.js.coffee b/app/assets/javascripts/line_highlighter.js.coffee
new file mode 100644
index 00000000000..a8b3c1fa33e
--- /dev/null
+++ b/app/assets/javascripts/line_highlighter.js.coffee
@@ -0,0 +1,148 @@
+# LineHighlighter
+#
+# Handles single- and multi-line selection and highlight for blob views.
+#
+#= require jquery.scrollTo
+#
+# ### Example Markup
+#
+# <div id="tree-content-holder">
+# <div class="file-content">
+# <div class="line-numbers">
+# <a href="#L1" id="L1" data-line-number="1">1</a>
+# <a href="#L2" id="L2" data-line-number="2">2</a>
+# <a href="#L3" id="L3" data-line-number="3">3</a>
+# <a href="#L4" id="L4" data-line-number="4">4</a>
+# <a href="#L5" id="L5" data-line-number="5">5</a>
+# </div>
+# <pre class="code highlight">
+# <code>
+# <span id="LC1" class="line">...</span>
+# <span id="LC2" class="line">...</span>
+# <span id="LC3" class="line">...</span>
+# <span id="LC4" class="line">...</span>
+# <span id="LC5" class="line">...</span>
+# </code>
+# </pre>
+# </div>
+# </div>
+#
+class @LineHighlighter
+ # CSS class applied to highlighted lines
+ highlightClass: 'hll'
+
+ # Internal copy of location.hash so we're not dependent on `location` in tests
+ _hash: ''
+
+ # Initialize a LineHighlighter object
+ #
+ # hash - String URL hash for dependency injection in tests
+ constructor: (hash = location.hash) ->
+ @_hash = hash
+
+ @bindEvents()
+
+ unless hash == ''
+ range = @hashToRange(hash)
+
+ if range[0]
+ @highlightRange(range)
+
+ # Scroll to the first highlighted line on initial load
+ # Offset -50 for the sticky top bar, and another -100 for some context
+ $.scrollTo("#L#{range[0]}", offset: -150)
+
+ bindEvents: ->
+ $('#tree-content-holder').on 'mousedown', 'a[data-line-number]', @clickHandler
+
+ # While it may seem odd to bind to the mousedown event and then throw away
+ # the click event, there is a method to our madness.
+ #
+ # If not done this way, the line number anchor will sometimes keep its
+ # active state even when the event is cancelled, resulting in an ugly border
+ # around the link and/or a persisted underline text decoration.
+
+ $('#tree-content-holder').on 'click', 'a[data-line-number]', (event) ->
+ event.preventDefault()
+
+ clickHandler: (event) =>
+ event.preventDefault()
+
+ @clearHighlight()
+
+ lineNumber = $(event.target).data('line-number')
+ current = @hashToRange(@_hash)
+
+ unless current[0] && event.shiftKey
+ # If there's no current selection, or there is but Shift wasn't held,
+ # treat this like a single-line selection.
+ @setHash(lineNumber)
+ @highlightLine(lineNumber)
+ else if event.shiftKey
+ if lineNumber < current[0]
+ range = [lineNumber, current[0]]
+ else
+ range = [current[0], lineNumber]
+
+ @setHash(range[0], range[1])
+ @highlightRange(range)
+
+ # Unhighlight previously highlighted lines
+ clearHighlight: ->
+ $(".#{@highlightClass}").removeClass(@highlightClass)
+
+ # Convert a URL hash String into line numbers
+ #
+ # hash - Hash String
+ #
+ # Examples:
+ #
+ # hashToRange('#L5') # => [5, null]
+ # hashToRange('#L5-15') # => [5, 15]
+ # hashToRange('#foo') # => [null, null]
+ #
+ # Returns an Array
+ hashToRange: (hash) ->
+ matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/)
+
+ if matches && matches.length
+ first = parseInt(matches[1])
+ last = if matches[2] then parseInt(matches[2]) else null
+
+ [first, last]
+ else
+ [null, null]
+
+ # Highlight a single line
+ #
+ # lineNumber - Line number to highlight
+ highlightLine: (lineNumber) =>
+ $("#LC#{lineNumber}").addClass(@highlightClass)
+
+ # Highlight all lines within a range
+ #
+ # range - Array containing the starting and ending line numbers
+ highlightRange: (range) ->
+ if range[1]
+ for lineNumber in [range[0]..range[1]]
+ @highlightLine(lineNumber)
+ else
+ @highlightLine(range[0])
+
+ # Set the URL hash string
+ setHash: (firstLineNumber, lastLineNumber) =>
+ if lastLineNumber
+ hash = "#L#{firstLineNumber}-#{lastLineNumber}"
+ else
+ hash = "#L#{firstLineNumber}"
+
+ @_hash = hash
+ @__setLocationHash__(hash)
+
+ # Make the actual hash change in the browser
+ #
+ # This method is stubbed in tests.
+ __setLocationHash__: (value) ->
+ # We're using pushState instead of assigning location.hash directly to
+ # prevent the page from scrolling on the hashchange event
+ history.pushState({turbolinks: false, url: value}, document.title, value)
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index 25a7815dba2..5c0bc686111 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -1,29 +1,25 @@
#= require jquery.waitforimages
#= require task_list
+#= require merge_request_tabs
+
class @MergeRequest
# Initialize MergeRequest behavior
#
# Options:
- # action - String, current controller action
- # diffs_loaded - Boolean, have diffs been pre-rendered server-side?
- # (default: true if `action` is 'diffs', otherwise false)
- # commits_loaded - Boolean, have commits been pre-rendered server-side?
- # (default: false)
+ # action - String, current controller action
#
constructor: (@opts) ->
@initContextWidget()
this.$el = $('.merge-request')
- @diffs_loaded = @opts.diffs_loaded or @opts.action == 'diffs'
- @commits_loaded = @opts.commits_loaded or false
-
- this.bindEvents()
- this.activateTabFromPath()
-
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
+ # `MergeRequests#new` has no tab-persisting or lazy-loading behavior
+ unless @opts.action == 'new'
+ new MergeRequestTabs(@opts)
+
# Prevent duplicate event bindings
@disableTaskList()
@@ -52,83 +48,6 @@ class @MergeRequest
$(".context .inline-update").on "change", "#merge_request_assignee_id", ->
$(this).submit()
-
- bindEvents: ->
- this.$('.merge-request-tabs a[data-toggle="tab"]').on 'shown.bs.tab', (e) =>
- $target = $(e.target)
- tab_action = $target.data('action')
-
- # Lazy-load diffs
- if tab_action == 'diffs'
- this.loadDiff() unless @diffs_loaded
- $('.diff-header').trigger('sticky_kit:recalc')
-
- # Skip tab-persisting behavior on MergeRequests#new
- unless @opts.action == 'new'
- @setCurrentAction(tab_action)
-
- # Activate a tab based on the current URL path
- #
- # If the current action is 'show' or 'new' (i.e., initial page load),
- # activates the first tab, otherwise activates the tab corresponding to the
- # current action (diffs, commits).
- activateTabFromPath: ->
- if @opts.action == 'show' || @opts.action == 'new'
- this.$('.merge-request-tabs a[data-toggle="tab"]:first').tab('show')
- else
- this.$(".merge-request-tabs a[data-action='#{@opts.action}']").tab('show')
-
- # Replaces the current Merge Request-specific action in the URL with a new one
- #
- # If the action is "notes", the URL is reset to the standard
- # `MergeRequests#show` route.
- #
- # Examples:
- #
- # location.pathname # => "/namespace/project/merge_requests/1"
- # setCurrentAction('diffs')
- # location.pathname # => "/namespace/project/merge_requests/1/diffs"
- #
- # location.pathname # => "/namespace/project/merge_requests/1/diffs"
- # setCurrentAction('notes')
- # location.pathname # => "/namespace/project/merge_requests/1"
- #
- # location.pathname # => "/namespace/project/merge_requests/1/diffs"
- # setCurrentAction('commits')
- # location.pathname # => "/namespace/project/merge_requests/1/commits"
- setCurrentAction: (action) ->
- # Normalize action, just to be safe
- action = 'notes' if action == 'show'
-
- # Remove a trailing '/commits' or '/diffs'
- new_state = location.pathname.replace(/\/(commits|diffs)\/?$/, '')
-
- # Append the new action if we're on a tab other than 'notes'
- unless action == 'notes'
- new_state += "/#{action}"
-
- # Ensure parameters and hash come along for the ride
- new_state += location.search + location.hash
-
- # Replace the current history state with the new one without breaking
- # Turbolinks' history.
- #
- # See https://github.com/rails/turbolinks/issues/363
- history.replaceState {turbolinks: true, url: new_state}, '', new_state
-
- loadDiff: (event) ->
- $.ajax
- type: 'GET'
- url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json"
- beforeSend: =>
- this.$('.mr-loading-status .loading').show()
- complete: =>
- @diffs_loaded = true
- this.$('.mr-loading-status .loading').hide()
- success: (data) =>
- this.$(".diffs").html(data.html)
- dataType: 'json'
-
showAllCommits: ->
this.$('.first-commits').remove()
this.$('.all-commits').removeClass 'hide'
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
new file mode 100644
index 00000000000..de9a4c2cc2f
--- /dev/null
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -0,0 +1,153 @@
+# MergeRequestTabs
+#
+# Handles persisting and restoring the current tab selection and lazily-loading
+# content on the MergeRequests#show page.
+#
+# ### Example Markup
+#
+# <ul class="nav nav-tabs merge-request-tabs">
+# <li class="notes-tab active">
+# <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1">
+# Discussion
+# </a>
+# </li>
+# <li class="commits-tab">
+# <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits">
+# Commits
+# </a>
+# </li>
+# <li class="diffs-tab">
+# <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs">
+# Diffs
+# </a>
+# </li>
+# </ul>
+#
+# <div class="tab-content">
+# <div class="notes tab-pane active" id="notes">
+# Notes Content
+# </div>
+# <div class="commits tab-pane" id="commits">
+# Commits Content
+# </div>
+# <div class="diffs tab-pane" id="diffs">
+# Diffs Content
+# </div>
+# </div>
+#
+# <div class="mr-loading-status">
+# <div class="loading">
+# Loading Animation
+# </div>
+# </div>
+#
+class @MergeRequestTabs
+ diffsLoaded: false
+ commitsLoaded: false
+
+ constructor: (@opts = {}) ->
+ # Store the `location` object, allowing for easier stubbing in tests
+ @_location = location
+
+ @bindEvents()
+ @activateTab(@opts.action)
+
+ switch @opts.action
+ when 'commits' then @commitsLoaded = true
+ when 'diffs' then @diffsLoaded = true
+
+ bindEvents: ->
+ $(document).on 'shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', @tabShown
+
+ tabShown: (event) =>
+ $target = $(event.target)
+ action = $target.data('action')
+
+ if action == 'commits'
+ @loadCommits($target.attr('href'))
+ else if action == 'diffs'
+ @loadDiff($target.attr('href'))
+
+ @setCurrentAction(action)
+
+ # Activate a tab based on the current action
+ activateTab: (action) ->
+ action = 'notes' if action == 'show'
+ $(".merge-request-tabs a[data-action='#{action}']").tab('show')
+
+ # Replaces the current Merge Request-specific action in the URL with a new one
+ #
+ # If the action is "notes", the URL is reset to the standard
+ # `MergeRequests#show` route.
+ #
+ # Examples:
+ #
+ # location.pathname # => "/namespace/project/merge_requests/1"
+ # setCurrentAction('diffs')
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ #
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ # setCurrentAction('notes')
+ # location.pathname # => "/namespace/project/merge_requests/1"
+ #
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ # setCurrentAction('commits')
+ # location.pathname # => "/namespace/project/merge_requests/1/commits"
+ #
+ # Returns the new URL String
+ setCurrentAction: (action) =>
+ # Normalize action, just to be safe
+ action = 'notes' if action == 'show'
+
+ # Remove a trailing '/commits' or '/diffs'
+ new_state = @_location.pathname.replace(/\/(commits|diffs)\/?$/, '')
+
+ # Append the new action if we're on a tab other than 'notes'
+ unless action == 'notes'
+ new_state += "/#{action}"
+
+ # Ensure parameters and hash come along for the ride
+ new_state += @_location.search + @_location.hash
+
+ # Replace the current history state with the new one without breaking
+ # Turbolinks' history.
+ #
+ # See https://github.com/rails/turbolinks/issues/363
+ history.replaceState {turbolinks: true, url: new_state}, document.title, new_state
+
+ new_state
+
+ loadCommits: (source) ->
+ return if @commitsLoaded
+
+ @_get
+ url: "#{source}.json"
+ success: (data) =>
+ document.getElementById('commits').innerHTML = data.html
+ $('.js-timeago').timeago()
+ @commitsLoaded = true
+
+ loadDiff: (source) ->
+ return if @diffsLoaded
+
+ @_get
+ url: "#{source}.json"
+ success: (data) =>
+ document.getElementById('diffs').innerHTML = data.html
+ $('.diff-header').trigger('sticky_kit:recalc')
+ @diffsLoaded = true
+
+ toggleLoading: ->
+ $('.mr-loading-status .loading').toggle()
+
+ _get: (options) ->
+ defaults = {
+ beforeSend: @toggleLoading
+ complete: @toggleLoading
+ dataType: 'json'
+ type: 'GET'
+ }
+
+ options = $.extend({}, defaults, options)
+
+ $.ajax(options)
diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss
index 8f17232592e..26eb7ab1a12 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/generic/header.scss
@@ -10,6 +10,10 @@ header {
.center-logo {
margin: 8px 0;
text-align: center;
+
+ img {
+ height: 32px;
+ }
}
}
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 17ddde68f93..d2f0c43929f 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -1,7 +1,7 @@
class DashboardController < Dashboard::ApplicationController
- before_action :load_projects, except: [:projects]
+ before_action :load_projects
before_action :event_filter, only: :show
-
+
respond_to :html
def show
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 145f27b67dd..8450ba31021 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -24,7 +24,7 @@ class PasswordsController < Devise::PasswordsController
super do |resource|
# TODO (rspeicher): In Devise master (> 3.4.1), we can set
# `Devise.sign_in_after_reset_password = false` and avoid this mess.
- if resource.errors.empty? && resource.try(:otp_required_for_login?)
+ if resource.errors.empty? && resource.try(:two_factor_enabled?)
resource.unlock_access! if unlockable?(resource)
# Since we are not signing this user in, we use the :updated_not_active
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index e7579c652fb..03845f1e1ec 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -10,7 +10,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def create
if current_user.valid_otp?(params[:pin_code])
- current_user.otp_required_for_login = true
+ current_user.two_factor_enabled = true
@codes = current_user.generate_otp_backup_codes!
current_user.save!
@@ -30,7 +30,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def destroy
current_user.update_attributes({
- otp_required_for_login: false,
+ two_factor_enabled: false,
encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil,
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 14069bafe71..51ecbfd561a 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -71,7 +71,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def commits
- render 'show'
+ respond_to do |format|
+ format.html { render 'show' }
+ format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } }
+ end
end
def new
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 4d976fe6630..7577fc96d6d 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -57,7 +57,7 @@ class SessionsController < Devise::SessionsController
def authenticate_with_two_factor
user = self.resource = find_user
- return unless user && user.otp_required_for_login
+ return unless user && user.two_factor_enabled?
if user_params[:otp_attempt].present? && session[:otp_user_id]
if valid_otp_attempt?(user)
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 0bed2115dc7..2eccc0ee31f 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -45,10 +45,10 @@ class IssuableFinder
def group
return @group if defined?(@group)
- @group =
+ @group =
if params[:group_id].present?
Group.find(params[:group_id])
- else
+ else
nil
end
end
@@ -56,10 +56,10 @@ class IssuableFinder
def project
return @project if defined?(@project)
- @project =
+ @project =
if params[:project_id].present?
Project.find(params[:project_id])
- else
+ else
nil
end
end
@@ -76,7 +76,7 @@ class IssuableFinder
return @milestones if defined?(@milestones)
@milestones =
- if milestones? && params[:milestone_title] != NONE
+ if milestones? && params[:milestone_title] != NONE
Milestone.where(title: params[:milestone_title])
else
nil
@@ -90,7 +90,7 @@ class IssuableFinder
def assignee
return @assignee if defined?(@assignee)
- @assignee =
+ @assignee =
if assignee? && params[:assignee_id] != NONE
User.find(params[:assignee_id])
else
@@ -105,7 +105,7 @@ class IssuableFinder
def author
return @author if defined?(@author)
- @author =
+ @author =
if author? && params[:author_id] != NONE
User.find(params[:author_id])
else
@@ -148,8 +148,6 @@ class IssuableFinder
case params[:state]
when 'closed'
items.closed
- when 'rejected'
- items.respond_to?(:rejected) ? items.rejected : items.closed
when 'merged'
items.respond_to?(:merged) ? items.merged : items.closed
when 'all'
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index bb8d5683807..14df8d4cbd7 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -16,6 +16,6 @@ module AppearancesHelper
end
def brand_header_logo
- image_tag 'logo-white.png'
+ image_tag 'logo.svg'
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 10d7aa11209..0b46db4b1c3 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -179,14 +179,33 @@ module ApplicationHelper
BroadcastMessage.current
end
- def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago')
- capture_haml do
- haml_tag :time, date.to_s,
- class: html_class, datetime: date.getutc.iso8601, title: date.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
- data: { toggle: 'tooltip', placement: placement }
-
- haml_tag :script, "$('." + html_class + "').timeago().tooltip()"
- end.html_safe
+ # Render a `time` element with Javascript-based relative date and tooltip
+ #
+ # time - Time object
+ # placement - Tooltip placement String (default: "top")
+ # html_class - Custom class for `time` element (default: "time_ago")
+ # skip_js - When true, exclude the `script` tag (default: false)
+ #
+ # By default also includes a `script` element with Javascript necessary to
+ # initialize the `timeago` jQuery extension. If this method is called many
+ # times, for example rendering hundreds of commits, it's advisable to disable
+ # this behavior using the `skip_js` argument and re-initializing `timeago`
+ # manually once all of the elements have been rendered.
+ #
+ # A `js-timeago` class is always added to the element, even when a custom
+ # `html_class` argument is provided.
+ #
+ # Returns an HTML-safe String
+ def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false)
+ element = content_tag :time, time.to_s,
+ class: "#{html_class} js-timeago",
+ datetime: time.getutc.iso8601,
+ title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
+ data: { toggle: 'tooltip', placement: placement }
+
+ element += javascript_tag "$('.js-timeago').timeago()" unless skip_js
+
+ element
end
def render_markup(file_name, file_content)
@@ -214,39 +233,6 @@ module ApplicationHelper
Gitlab::MarkupHelper.asciidoc?(filename)
end
- # Overrides ActionView::Helpers::UrlHelper#link_to to add `rel="nofollow"` to
- # external links
- def link_to(name = nil, options = nil, html_options = {})
- if options.kind_of?(String)
- if !options.start_with?('#', '/')
- html_options = add_nofollow(options, html_options)
- end
- end
-
- super
- end
-
- # Add `"rel=nofollow"` to external links
- #
- # link - String link to check
- # html_options - Hash of `html_options` passed to `link_to`
- #
- # Returns `html_options`, adding `rel: nofollow` for external links
- def add_nofollow(link, html_options = {})
- begin
- uri = URI(link)
-
- if uri && uri.absolute? && uri.host != Gitlab.config.gitlab.host
- rel = html_options.fetch(:rel, '')
- html_options[:rel] = (rel + ' nofollow').strip
- end
- rescue URI::Error
- # noop
- end
-
- html_options
- end
-
def promo_host
'about.gitlab.com'
end
@@ -295,10 +281,9 @@ module ApplicationHelper
def state_filters_text_for(entity, project)
titles = {
- opened: "Open",
- merged: "Accepted"
+ opened: "Open"
}
-
+
entity_title = titles[entity] || entity.to_s.humanize
count =
diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb
index 29ff47663da..6484dca6b55 100644
--- a/app/helpers/broadcast_messages_helper.rb
+++ b/app/helpers/broadcast_messages_helper.rb
@@ -1,9 +1,16 @@
module BroadcastMessagesHelper
def broadcast_styling(broadcast_message)
- if(broadcast_message.color || broadcast_message.font)
- "background-color:#{broadcast_message.color};color:#{broadcast_message.font}"
- else
- ""
+ styling = ''
+
+ if broadcast_message.color.present?
+ styling << "background-color: #{broadcast_message.color}"
+ styling << '; ' if broadcast_message.font.present?
end
+
+ if broadcast_message.font.present?
+ styling << "color: #{broadcast_message.font}"
+ end
+
+ styling
end
end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index a730684f8f3..30b17a736a7 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -1,4 +1,6 @@
module IconsHelper
+ include FontAwesome::Rails::IconHelper
+
# Creates an icon tag given icon name(s) and possible icon modifiers.
#
# Right now this method simply delegates directly to `fa_icon` from the
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 36d3f371c1b..d4c345fe431 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -45,13 +45,13 @@ module IssuesHelper
def issue_timestamp(issue)
# Shows the created at time and the updated at time if different
- ts = "#{time_ago_with_tooltip(issue.created_at, 'bottom', 'note_created_ago')}"
+ ts = time_ago_with_tooltip(issue.created_at, placement: 'bottom', html_class: 'note_created_ago')
if issue.updated_at != issue.created_at
ts << capture_haml do
haml_tag :span do
haml_concat '&middot;'
haml_concat icon('edit', title: 'edited')
- haml_concat time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago')
+ haml_concat time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
end
end
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index a7c1fa0b071..dda9b17d61d 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -25,13 +25,13 @@ module NotesHelper
def note_timestamp(note)
# Shows the created at time and the updated at time if different
- ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')}"
+ ts = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
if note.updated_at != note.created_at
ts << capture_haml do
haml_tag :span do
haml_concat '&middot;'
haml_concat icon('edit', title: 'edited')
- haml_concat time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago')
+ haml_concat time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago')
end
end
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index f771fe761ef..2f8e64c375f 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -1,4 +1,6 @@
module NotificationsHelper
+ include IconsHelper
+
def notification_icon(notification)
if notification.disabled?
icon('volume-off', class: 'ns-mute')
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 94ce6646634..ec65e473919 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -211,7 +211,7 @@ module ProjectsHelper
def project_last_activity(project)
if project.last_activity_at
- time_ago_with_tooltip(project.last_activity_at, 'bottom', 'last_activity_time_ago')
+ time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago')
else
"Never"
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index bcd2adee00b..a5db22040e0 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -263,7 +263,7 @@ class Ability
:"modify_#{name}",
]
else
- if subject.respond_to?(:project)
+ if subject.respond_to?(:project) && subject.project
project_abilities(user, subject.project)
else
[]
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 10c39cb1ece..56849f28ff0 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -75,7 +75,7 @@ module Mentionable
refs.reject! { |ref| without.include?(ref) }
refs.each do |ref|
- Note.create_cross_reference_note(ref, local_reference, a)
+ SystemNoteService.cross_reference(ref, local_reference, a)
end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 487d62e65b6..7ecdaf6b2e0 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -125,16 +125,14 @@ class MergeRequest < ActiveRecord::Base
validate :validate_fork
scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
- scope :merged, -> { with_state(:merged) }
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
scope :of_projects, ->(ids) { where(target_project_id: ids) }
- # Closed scope for merge request should return
- # both merged and closed mr's
- scope :closed, -> { with_states(:closed, :merged) }
- scope :rejected, -> { with_states(:closed) }
+ scope :merged, -> { with_state(:merged) }
+ scope :closed, -> { with_state(:closed) }
+ scope :closed_and_merged, -> { with_states(:closed, :merged) }
def self.reference_prefix
'!'
@@ -417,4 +415,14 @@ class MergeRequest < ActiveRecord::Base
def can_be_merged_by?(user)
::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch)
end
+
+ def state_human_name
+ if merged?
+ "Merged"
+ elsif closed?
+ "Closed"
+ else
+ "Open"
+ end
+ end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 9c543b37023..e0c5fec97b7 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -56,7 +56,7 @@ class Milestone < ActiveRecord::Base
end
def closed_items_count
- self.issues.closed.count + self.merge_requests.closed.count
+ self.issues.closed.count + self.merge_requests.closed_and_merged.count
end
def total_items_count
diff --git a/app/models/note.rb b/app/models/note.rb
index 6a74d62b715..68b9d433ae0 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -63,11 +63,6 @@ class Note < ActiveRecord::Base
after_update :set_references
class << self
- # TODO (rspeicher): Update usages
- def create_cross_reference_note(*args)
- SystemNoteService.cross_reference(*args)
- end
-
def discussions_from_notes(notes)
discussion_ids = []
discussions = []
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 2c6347222aa..b32e8847bb5 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -5,8 +5,13 @@ class Repository
def initialize(path_with_namespace, default_branch = nil, project = nil)
@path_with_namespace = path_with_namespace
- @raw_repository = Gitlab::Git::Repository.new(path_to_repo) if path_with_namespace
@project = project
+
+ if path_with_namespace
+ @raw_repository = Gitlab::Git::Repository.new(path_to_repo)
+ @raw_repository.autocrlf = :input
+ end
+
rescue Gitlab::Git::Repository::NoRepository
nil
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 3ab9e834c63..b0831982aa7 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -34,7 +34,6 @@ class Snippet < ActiveRecord::Base
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
validates :file_name,
- presence: true,
length: { within: 0..255 },
format: { with: Gitlab::Regex.file_name_regex,
message: Gitlab::Regex.file_name_regex_message }
diff --git a/app/models/user.rb b/app/models/user.rb
index 982c05212ce..29f43051464 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -172,6 +172,9 @@ class User < ActiveRecord::Base
after_create :post_create_hook
after_destroy :post_destroy_hook
+ # User's Dashboard preference
+ # Note: When adding an option, it MUST go on the end of the array.
+ enum dashboard: [:projects, :stars]
alias_attribute :private_token, :authentication_token
@@ -220,10 +223,26 @@ class User < ActiveRecord::Base
end
def find_for_commit(email, name)
- # Prefer email match over name match
- User.where(email: email).first ||
- User.joins(:emails).where(emails: { email: email }).first ||
- User.where(name: name).first
+ user_table = arel_table
+ email_table = Email.arel_table
+
+ # Use ARel to build a query:
+ query = user_table.
+ # SELECT "users".* FROM "users"
+ project(user_table[Arel.star]).
+ # LEFT OUTER JOIN "emails"
+ join(email_table, Arel::Nodes::OuterJoin).
+ # ON "users"."id" = "emails"."user_id"
+ on(user_table[:id].eq(email_table[:user_id])).
+ # WHERE ("user"."email" = '<email>' OR "user"."name" = '<name>')
+ # OR "emails"."email" = '<email>'
+ where(
+ user_table[:email].eq(email).
+ or(user_table[:name].eq(name)).
+ or(email_table[:email].eq(email))
+ )
+
+ find_by_sql(query.to_sql).first
end
def filter(filter_name)
@@ -297,6 +316,18 @@ class User < ActiveRecord::Base
@reset_token
end
+ # Check if the user has enabled Two-factor Authentication
+ def two_factor_enabled?
+ otp_required_for_login
+ end
+
+ # Set whether or not Two-factor Authentication is enabled for the current user
+ #
+ # setting - Boolean
+ def two_factor_enabled=(setting)
+ self.otp_required_for_login = setting
+ end
+
def namespace_uniq
namespace_name = self.username
existing_namespace = Namespace.by_path(namespace_name)
@@ -704,8 +735,4 @@ class User < ActiveRecord::Base
def can_be_removed?
!solo_owned_groups.present?
end
-
- # User's Dashboard preference
- # Note: When adding an option, it MUST go on the end of the array.
- enum dashboard: [:projects, :stars]
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 68d3b915fc9..6135ae65007 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -105,7 +105,7 @@ class GitPushService
author ||= commit_user(commit)
refs.each do |r|
- Note.create_cross_reference_note(r, commit, author)
+ SystemNoteService.cross_reference(r, commit, author)
end
end
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 0ff37c41743..482c0444049 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -15,7 +15,7 @@ module Notes
# Create a cross-reference note if this Note contains GFM that names an
# issue, merge request, or commit.
note.references.each do |mentioned|
- Note.create_cross_reference_note(mentioned, note.noteable, note.author)
+ SystemNoteService.cross_reference(mentioned, note.noteable, note.author)
end
execute_hooks(note)
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 45a0db761ec..b5611d46257 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -13,7 +13,7 @@ module Notes
# Create a cross-reference note if this Note contains GFM that
# names an issue, merge request, or commit.
note.references.each do |mentioned|
- Note.create_cross_reference_note(mentioned, note.noteable, note.author)
+ SystemNoteService.cross_reference(mentioned, note.noteable, note.author)
end
end
end
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 69918193e8a..0b8260964fe 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -38,6 +38,14 @@
= link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-xs btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do
%i.fa.fa-times
+ %li.two-factor-status
+ %span.light Two-factor Authentication:
+ %strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'}
+ - if @user.two_factor_enabled?
+ Enabled
+ - else
+ Disabled
+
%li
%span.light Can create groups:
%strong
diff --git a/app/views/layouts/header/_empty.html.haml b/app/views/layouts/header/_empty.html.haml
index a52a3c8f0ef..2ed4edb1136 100644
--- a/app/views/layouts/header/_empty.html.haml
+++ b/app/views/layouts/header/_empty.html.haml
@@ -1,4 +1,4 @@
%header.navbar.navbar-fixed-top.navbar-empty
.container
.center-logo
- = image_tag 'logo-white.png', width: 32, height: 32
+ = brand_header_logo
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index ed009c86568..378dfa2dce0 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -36,7 +36,7 @@
.panel-heading
Two-factor Authentication
.panel-body
- - if current_user.otp_required_for_login
+ - if current_user.two_factor_enabled?
.pull-right
= link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm',
data: { confirm: 'Are you sure?' }
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
index 4d93c89c93a..496fad34dc2 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/projects/_issuable_form.html.haml
@@ -15,10 +15,10 @@
- if issuable.is_a?(MergeRequest)
%p.help-block
- if issuable.work_in_progress?
- Remove the <code>WIP</code> prefix from the title to allow this
+ Remove the <code>WIP</code> prefix from the title to allow this
<strong>Work In Progress</strong> merge request to be accepted when it's ready.
- else
- Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
+ Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
<strong>Work In Progress</strong> merge request from being accepted before it's ready.
.form-group.issuable-description
= f.label :description, 'Description', class: 'control-label'
@@ -81,21 +81,22 @@
- if issuable.is_a?(MergeRequest)
%hr
- - unless @merge_request.persisted?
+ - if @merge_request.new_record?
.form-group
= f.label :source_branch, class: 'control-label' do
%i.fa.fa-code-fork
Source Branch
.col-sm-10
= f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
- %p.help-block
- = link_to 'Change source branch', mr_change_branches_path(@merge_request)
.form-group
= f.label :target_branch, class: 'control-label' do
%i.fa.fa-code-fork
Target Branch
.col-sm-10
- = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2' })
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record? })
+ - if @merge_request.new_record?
+ %p.help-block
+ = link_to 'Change branches', mr_change_branches_path(@merge_request)
.form-actions
- if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted?
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 083fca9b658..f9106564a27 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -29,5 +29,5 @@
= commit_author_link(commit, avatar: true, size: 24)
authored
.committed_ago
- #{time_ago_with_tooltip(commit.committed_date)} &nbsp;
+ #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} &nbsp;
= link_to_browse_code(project, commit)
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 64d62b45657..2c296cab977 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -28,7 +28,7 @@
= 0
.issue-info
- = "##{issue.iid} opened #{time_ago_with_tooltip(issue.created_at, 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
+ = "##{issue.iid} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
- if issue.votes_count > 0
= render 'votes/votes_inline', votable: issue
- if issue.milestone
@@ -41,4 +41,4 @@
= issue.task_status
.pull-right.issue-updated-at
- %small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')}
+ %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index c16df27ee8f..0bcd543fee7 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -9,11 +9,11 @@
- if merge_request.merged?
%span
%i.fa.fa-check
- ACCEPTED
+ MERGED
- elsif merge_request.closed?
%span
%i.fa.fa-ban
- REJECTED
+ CLOSED
- else
%span.hidden-xs.hidden-sm
%span.label-branch<
@@ -35,7 +35,7 @@
= 0
.merge-request-info
- = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe
+ = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe
- if merge_request.votes_count > 0
= render 'votes/votes_inline', votable: merge_request
- if merge_request.milestone_id?
@@ -48,4 +48,4 @@
= merge_request.task_status
.pull-right.hidden-xs
- %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')}
+ %small updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 5d7e73f2b28..9dc4a47258e 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -56,7 +56,8 @@
#notes.notes.tab-pane.voting_notes
= render "projects/merge_requests/discussion"
#commits.commits.tab-pane
- = render "projects/merge_requests/show/commits"
+ - if current_page?(action: 'commits')
+ = render "projects/merge_requests/show/commits"
#diffs.diffs.tab-pane
- if current_page?(action: 'diffs')
= render "projects/merge_requests/show/diffs"
@@ -64,7 +65,6 @@
.mr-loading-status
= spinner
-
:javascript
var merge_request;
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 0690fdb769f..83baf157a92 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,11 +1,6 @@
%h4.page-title
.issue-box{ class: issue_box_class(@merge_request) }
- - if @merge_request.merged?
- Accepted
- - elsif @merge_request.closed?
- Rejected
- - else
- Open
+ = @merge_request.state_human_name
= "Merge Request ##{@merge_request.iid}"
%small.creator
&middot;
diff --git a/app/views/projects/merge_requests/widget/_closed.html.haml b/app/views/projects/merge_requests/widget/_closed.html.haml
index 18164ba771f..b5704c502c8 100644
--- a/app/views/projects/merge_requests/widget/_closed.html.haml
+++ b/app/views/projects/merge_requests/widget/_closed.html.haml
@@ -2,7 +2,7 @@
= render 'projects/merge_requests/widget/heading'
.mr-widget-body
%h4
- Rejected
+ Closed
- if @merge_request.closed_event
by #{link_to_member(@project, @merge_request.closed_event.author, avatar: true)}
#{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index 17c3fdacda8..a3b13140810 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -2,7 +2,7 @@
= render 'projects/merge_requests/widget/heading'
.mr-widget-body
%h4
- Accepted
+ Merged
- if @merge_request.merge_event
by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)}
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 417eaa1b09d..5c85092a045 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -86,10 +86,10 @@
.col-md-3
= render('merge_requests', title: 'Waiting for merge (open and assigned)', merge_requests: @merge_requests.opened.assigned, id: 'ongoing')
.col-md-3
- = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.rejected, id: 'closed')
+ = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.closed, id: 'closed')
.col-md-3
.panel.panel-primary
- .panel-heading Accepted
+ .panel-heading Merged
%ul.well-list
- @merge_requests.merged.each do |merge_request|
= render 'merge_request', merge_request: merge_request
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
index e7a3854701c..4f15a99d061 100644
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ b/app/views/projects/notes/discussions/_active.html.haml
@@ -16,7 +16,7 @@
= link_to_member(@project, last_note.author, avatar: false)
%span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')}
-
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
+
.discussion-body.js-toggle-content
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index 62609cfc1c8..6903fad4a0a 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -14,7 +14,7 @@
last updated by
= link_to_member(@project, last_note.author, avatar: false)
%span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')}
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content
- if note.for_diff_line?
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml
index 52a1d342f55..218b0da3977 100644
--- a/app/views/projects/notes/discussions/_outdated.html.haml
+++ b/app/views/projects/notes/discussions/_outdated.html.haml
@@ -14,6 +14,6 @@
last updated by
= link_to_member(@project, last_note.author, avatar: false)
%span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')}
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content.hide
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index adfdd1c7506..2efa616d664 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -11,6 +11,6 @@
#{merge_request.project.name_with_namespace}
.pull-right
- if merge_request.merged?
- %span.label.label-primary Accepted
+ %span.label.label-primary Merged
- elsif merge_request.closed?
- %span.label.label-danger Rejected
+ %span.label.label-danger Closed
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 86921f0a777..d6a2e177da1 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,11 +1,11 @@
.file-content.code{class: user_color_scheme_class}
.line-numbers
- if blob.data.present?
- - blob.data.lines.to_a.size.times do |index|
+ - 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}", rel: "#L#{i}"}
+ -# 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}
%i.fa.fa-link
= i
:preserve
diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml
index a5187fa4ea7..a355eb62813 100644
--- a/app/views/shared/_issuable_filter.html.haml
+++ b/app/views/shared/_issuable_filter.html.haml
@@ -12,10 +12,10 @@
= icon('check-circle')
#{state_filters_text_for(:merged, @project)}
- %li{class: ("active" if params[:state] == 'rejected')}
- = link_to page_filter_path(state: 'rejected') do
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to page_filter_path(state: 'closed') do
= icon('ban')
- #{state_filters_text_for(:rejected, @project)}
+ #{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed') do
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index fe25133abb0..913b6744844 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -18,7 +18,7 @@
.col-sm-10
.file-holder.snippet
.file-title
- = f.text_field :file_name, placeholder: "example.rb", class: 'form-control snippet-file-name', required: true
+ = f.text_field :file_name, placeholder: "Optionally name this file to add code highlighting, e.g. example.rb for Ruby.", class: 'form-control snippet-file-name'
.file-content.code
%pre#editor= @snippet.content
= f.hidden_field :content, class: 'snippet-file-content'
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 1694818aef6..15d53499e03 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -9,7 +9,8 @@
.row
%section.col-md-8
.header-with-avatar
- = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
+ = link_to avatar_icon(@user.email), target: '_blank' do
+ = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
%h3
= @user.name
- if @user == current_user