Commit 33ea09bd authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into fix/private-references

parents 0c10aee5 12023557
Please view this file on the master branch, on stable branches it's out of date.
v 8.4.0 (unreleased)
- Autocomplete data is now always loaded, instead of when focusing a comment text area (Yorick Peterse)
- Improved performance of finding issues for an entire group (Yorick Peterse)
- Added custom application performance measuring system powered by InfluxDB (Yorick Peterse)
- Bump fog to 1.36.0 (Stan Hu)
- Add user's last used IP addresses to admin page (Stan Hu)
- Add housekeeping function to project settings page
- The default GitLab logo now acts as a loading indicator
- Fix caching issue where build status was not updating in project dashboard (Stan Hu)
......@@ -39,6 +43,10 @@ v 8.4.0 (unreleased)
- API: Add support for deleting a tag via the API (Robert Schilling)
- Allow subsequent validations in CI Linter
- Show referenced MRs & Issues only when the current viewer can access them
- Fix Encoding::CompatibilityError bug when markdown content has some complex URL (Jason Lee)
v 8.3.4
- Use gitlab-workhorse 0.5.4 (fixes API routing bug)
v 8.3.3
- Preserve CE behavior with JIRA integration by only calling API if URL is set
......@@ -53,6 +61,7 @@ v 8.3.3
- Fix Error 500 when visiting build page of project with nil runners_token (Stan Hu)
- Use WOFF versions of SourceSansPro fonts
- Fix regression when builds were not generated for tags created through web/api interface
- Fix: maintain milestone filter between Open and Closed tabs (Greg Smethells)
v 8.3.2
- Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu)
......
......@@ -334,9 +334,9 @@ merge request:
1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript)
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
contributors to enhance security
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
1. [Database Migrations](doc/development/migration_style_guide.md)
1. [Documentation styleguide](doc_styleguide.md)
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
1. [Documentation styleguide](doc/development/doc_styleguide.md)
1. Interface text should be written subjectively instead of objectively. It
should be the GitLab core team addressing a person. It should be written in
present time and never use past tense (has been/was). For example instead
......
......@@ -247,7 +247,7 @@ group :development, :test do
gem 'byebug', platform: :mri
gem 'pry-rails'
gem 'awesome_print', '~> 1.2.0'
gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0'
gem 'database_cleaner', '~> 1.4.0'
......
......@@ -16,12 +16,16 @@ class @Issue
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
initIssueBtnEventListeners: ->
_this = @
issueFailMessage = 'Unable to update this issue at this time.'
$('a.btn-close, a.btn-reopen').on 'click', (e) ->
e.preventDefault()
e.stopImmediatePropagation()
$this = $(this)
isClose = $this.hasClass('btn-close')
shouldSubmit = $this.hasClass('btn-comment')
if shouldSubmit
_this.submitNoteForm($this.closest('form'))
$this.prop('disabled', true)
url = $this.attr('href')
$.ajax
......@@ -32,12 +36,13 @@ class @Issue
new Flash(issueFailMessage, 'alert')
success: (data, textStatus, jqXHR) ->
if data.saved
$this.addClass('hidden')
if isClose
$('a.btn-close').addClass('hidden')
$('a.btn-reopen').removeClass('hidden')
$('div.status-box-closed').removeClass('hidden')
$('div.status-box-open').addClass('hidden')
else
$('a.btn-reopen').addClass('hidden')
$('a.btn-close').removeClass('hidden')
$('div.status-box-closed').addClass('hidden')
$('div.status-box-open').removeClass('hidden')
......@@ -45,6 +50,11 @@ class @Issue
new Flash(issueFailMessage, 'alert')
$this.prop('disabled', false)
submitNoteForm: (form) =>
noteText = form.find("textarea.js-note-text").val()
if noteText.trim().length > 0
form.submit()
disableTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('disable')
$(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container'
......
......@@ -19,6 +19,7 @@ class @MergeRequest
# Prevent duplicate event bindings
@disableTaskList()
@initMRBtnListeners()
if $("a.btn-close").length
@initTaskList()
......@@ -43,6 +44,27 @@ class @MergeRequest
$('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
initMRBtnListeners: ->
_this = @
$('a.btn-close, a.btn-reopen').on 'click', (e) ->
$this = $(this)
if $this.data('submitted')
return
e.preventDefault()
e.stopImmediatePropagation()
shouldSubmit = $this.hasClass('btn-comment')
console.log("shouldSubmit")
if shouldSubmit
_this.submitNoteForm($this.closest('form'),$this)
submitNoteForm: (form, $button) =>
noteText = form.find("textarea.js-note-text").val()
if noteText.trim().length > 0
form.submit()
$button.data('submitted',true)
$button.trigger('click')
disableTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('disable')
$(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container'
......
......@@ -33,8 +33,6 @@ class @Notes
$(document).on "click", ".note-edit-cancel", @cancelEdit
# Reopen and close actions for Issue/MR combined with note form submit
$(document).on "click", ".js-note-target-reopen", @targetReopen
$(document).on "click", ".js-note-target-close", @targetClose
$(document).on "click", ".js-comment-button", @updateCloseButton
$(document).on "keyup", ".js-note-text", @updateTargetButtons
......@@ -512,17 +510,6 @@ class @Notes
visibilityChange: =>
@refresh()
targetReopen: (e) =>
@submitNoteForm($(e.target).parents('form'))
targetClose: (e) =>
@submitNoteForm($(e.target).parents('form'))
submitNoteForm: (form) =>
noteText = form.find(".js-note-text").val()
if noteText.trim().length > 0
form.submit()
updateCloseButton: (e) =>
textarea = $(e.target)
form = textarea.parents('form')
......@@ -531,7 +518,6 @@ class @Notes
updateTargetButtons: (e) =>
textarea = $(e.target)
form = textarea.parents('form')
if textarea.val().trim().length > 0
form.find('.js-note-target-reopen').text('Comment & reopen')
form.find('.js-note-target-close').text('Comment & close')
......
......@@ -74,7 +74,7 @@
/** light list with border-bottom between li **/
ul.bordered-list {
ul.bordered-list, ul.unstyled-list {
@include basic-list;
&.top-list {
......@@ -88,6 +88,10 @@ ul.bordered-list {
}
}
ul.unstyled-list > li {
border-bottom: none;
}
ul.task-list {
li.task-list-item {
list-style-type: none;
......
......@@ -24,6 +24,7 @@ $gl-gray: #5a5a5a;
$gl-padding: 16px;
$gl-padding-top:10px;
$gl-avatar-size: 46px;
$secondary-text: #7f8fa4;
/*
* Color schema
......
......@@ -144,3 +144,8 @@ form.edit-issue {
.issue-form .select2-container {
width: 250px !important;
}
.issue-closed-by-widget {
color: $secondary-text;
margin-left: 52px;
}
\ No newline at end of file
......@@ -6,11 +6,9 @@ class Admin::AbuseReportsController < Admin::ApplicationController
def destroy
abuse_report = AbuseReport.find(params[:id])
if params[:remove_user]
abuse_report.user.destroy
end
abuse_report.remove_user if params[:remove_user]
abuse_report.destroy
render nothing: true
end
end
......@@ -73,6 +73,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_pool_size,
:metrics_timeout,
:metrics_method_call_threshold,
:metrics_sample_interval,
:recaptcha_enabled,
:recaptcha_site_key,
:recaptcha_private_key,
......
......@@ -205,7 +205,7 @@ module ApplicationHelper
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 js-timeago-pending",
datetime: time.getutc.iso8601,
datetime: time.to_time.getutc.iso8601,
title: time.in_time_zone.to_s(:medium),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
......@@ -266,7 +266,7 @@ module ApplicationHelper
state: params[:state],
scope: params[:scope],
label_name: params[:label_name],
milestone_id: params[:milestone_id],
milestone_title: params[:milestone_title],
assignee_id: params[:assignee_id],
author_id: params[:author_id],
sort: params[:sort],
......
......@@ -67,7 +67,7 @@ module NotesHelper
line_type: line_type
}
button_tag class: 'btn reply-btn js-discussion-reply-button',
button_tag class: 'btn btn-nr reply-btn js-discussion-reply-button',
data: data, title: 'Add a reply' do
link_text = icon('comment')
link_text << ' Reply'
......
......@@ -19,6 +19,11 @@ class AbuseReport < ActiveRecord::Base
validates :message, presence: true
validates :user_id, uniqueness: true
def remove_user
user.block
user.destroy
end
def notify
return unless self.persisted?
......
......@@ -48,8 +48,8 @@ class WebHook < ActiveRecord::Base
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = {
username: URI.decode(parsed_url.user),
password: URI.decode(parsed_url.password),
username: CGI.unescape(parsed_url.user),
password: CGI.unescape(parsed_url.password),
}
response = WebHook.post(post_url,
body: data.to_json,
......
......@@ -120,13 +120,13 @@ class HipchatService < Service
message << "#{push[:user_name]} "
if Gitlab::Git.blank_ref?(before)
message << "pushed new #{ref_type} <a href=\""\
"#{project_url}/commits/#{URI.escape(ref)}\">#{ref}</a>"\
"#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"\
" to #{project_link}\n"
elsif Gitlab::Git.blank_ref?(after)
message << "removed #{ref_type} <b>#{ref}</b> from <a href=\"#{project.web_url}\">#{project_name}</a> \n"
else
message << "pushed to #{ref_type} <a href=\""\
"#{project.web_url}/commits/#{URI.escape(ref)}\">#{ref}</a> "
"#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> "
message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> "
message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
......@@ -255,8 +255,8 @@ class HipchatService < Service
status = data[:commit][:status]
duration = data[:commit][:duration]
branch_link = "<a href=\"#{project_url}/commits/#{URI.escape(ref)}\">#{ref}</a>"
commit_link = "<a href=\"#{project_url}/commit/#{URI.escape(sha)}/builds\">#{Commit.truncate_sha(sha)}</a>"
branch_link = "<a href=\"#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"
commit_link = "<a href=\"#{project_url}/commit/#{CGI.escape(sha)}/builds\">#{Commit.truncate_sha(sha)}</a>"
"#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
end
......
......@@ -73,9 +73,10 @@ class IrkerService < Service
'irc[s]://irc.network.net[:port]/#channel. Special cases: if '\
'you want the channel to be a nickname instead, append ",isnick" to ' \
'the channel name; if the channel is protected by a secret password, ' \
' append "?key=secretpassword" to the URI. Note that if you specify a ' \
' default IRC URI to prepend before each recipient, you can just give ' \
' a channel name.' },
' append "?key=secretpassword" to the URI (Note that due to a bug, if you ' \
' want to use a password, you have to omit the "#" on the channel). If you ' \
' specify a default IRC URI to prepend before each recipient, you can just ' \
' give a channel name.' },
{ type: 'checkbox', name: 'colorize_messages' },
]
end
......
......@@ -47,7 +47,8 @@ class SystemHooksService
data.merge!({
name: model.name,
email: model.email,
user_id: model.id
user_id: model.id,
username: model.username
})
when ProjectMember
data.merge!(project_member_data(model))
......@@ -99,8 +100,10 @@ class SystemHooksService
project_path: model.project.path,
project_path_with_namespace: model.project.path_with_namespace,
project_id: model.project.id,
user_username: model.user.username,
user_name: model.user.name,
user_email: model.user.email,
user_id: model.user.id,
access_level: model.human_access,
project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase
}
......@@ -111,6 +114,7 @@ class SystemHooksService
group_name: model.group.name,
group_path: model.group.path,
group_id: model.group.id,
user_username: model.user.username,
user_name: model.user.name,
user_email: model.user.email,
user_id: model.user.id,
......
......@@ -202,6 +202,13 @@
.help-block
A method call is only tracked when it takes longer to complete than
the given amount of milliseconds.
.form-group
= f.label :metrics_sample_interval, 'Sampler Interval (sec)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :metrics_sample_interval, class: 'form-control'
.help-block
The sampling interval in seconds. Sampled data includes memory usage,
retained Ruby objects, file descriptors and so on.
%fieldset
%legend Spam and Anti-bot Protection
......
- page_title "Labels"
= link_to new_admin_label_path, class: "pull-right btn btn-new" do
= link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do
New label
%h3.page-title
Labels
......
- page_title @project.name_with_namespace, "Projects"
%h3.page-title
Project: #{@project.name_with_namespace}
= link_to edit_project_path(@project), class: "btn pull-right" do
= link_to edit_project_path(@project), class: "btn btn-nr pull-right" do
%i.fa.fa-pencil-square-o
Edit
%hr
......
......@@ -7,8 +7,8 @@
.pull-right
- unless @user == current_user || @user.blocked?
= link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info"
= link_to edit_admin_user_path(@user), class: "btn btn-grouped" do
= link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-nr btn-grouped btn-info"
= link_to edit_admin_user_path(@user), class: "btn btn-nr btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
%hr
......
......@@ -70,6 +70,14 @@
%strong.cred
No
%li
%span.light Current sign-in IP:
%strong
- if @user.current_sign_in_ip
= @user.current_sign_in_ip
- else
never
%li
%span.light Current sign-in at:
%strong
......@@ -78,6 +86,14 @@
- else
never
%li
%span.light Last sign-in IP:
%strong
- if @user.last_sign_in_ip
= @user.last_sign_in_ip
- else
never
%li
%span.light Last sign-in at:
%strong
......
......@@ -16,7 +16,7 @@
- if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close the milestone now.
%span All issues for this milestone are closed. Navigate to the project to close the milestone.
.table-holder
%table.table
......
......@@ -4,7 +4,7 @@
.col-sm-10
= users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true)
.help-block
Search for existing users or invite new ones using their email address.
Search for users by name, username, or email, or invite new ones using their email address.
.form-group
= f.label :access_level, "Group Access", class: 'control-label'
......
.issue-closed-by-widget.gray-content-block.second-block.white
This issue will be closed automatically when merge request #{markdown(merge_requests_sentence(@closed_by_merge_requests), pipeline: :gfm)} is accepted.
.issue-closed-by-widget.second-block
- pluralized_mr_this = merge_request_count > 1 ? "these" : "this"
- pluralized_mr_is = merge_request_count > 1 ? "are" : "is"
When #{pluralized_mr_this} merge #{"request".pluralize(merge_request_count)} #{pluralized_mr_is} accepted, this issue will be closed automatically.
- content_for :note_actions do
- if can?(current_user, :update_issue, @issue)
- if @issue.closed?
= link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue'
- else
= link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-close js-note-target-close', title: 'Close Issue'
= link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue'
= link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue'
#notes
= render 'projects/notes/notes_with_form'
-if @merge_requests.any?
%h2.merge-requests-title
= pluralize(@merge_requests.count, 'Related Merge Request')
%ul.bordered-list
%ul.unstyled-list
- has_any_ci = @merge_requests.any?(&:ci_commit)
- @merge_requests.each do |merge_request|
%li
......@@ -11,7 +11,7 @@
- elsif has_any_ci
= icon('blank fw')
%span.merge-request-id
\##{merge_request.iid}
\!#{merge_request.iid}
%span.merge-request-info
%strong
= link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
......@@ -24,3 +24,5 @@
MERGED
- elsif merge_request.closed?
CLOSED
- if @closed_by_merge_requests.present?
= render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
......@@ -53,9 +53,6 @@
.gray-content-block.second-block.oneline-block
= render 'votes/votes_block', votable: @issue
- if @closed_by_merge_requests.present?
= render 'projects/issues/closed_by_box'
.row
%section.col-md-9
.issuable-discussion
......
- content_for :note_actions do
- if can?(current_user, :update_merge_request, @merge_request)
- if @merge_request.open?
= link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
= link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
- if @merge_request.closed?
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
#notes= render "projects/notes/notes_with_form"
......@@ -6,5 +6,5 @@
= render 'projects/notes/hints'
.note-form-actions
= f.submit 'Save Comment', class: 'btn btn-primary btn-save btn-grouped js-comment-button'
= link_to 'Cancel', '#', class: 'btn btn-cancel note-edit-cancel'
= f.submit 'Save Comment', class: 'btn btn-nr btn-save btn-grouped js-comment-button'
= link_to 'Cancel', '#', class: 'btn btn-nr btn-cancel note-edit-cancel'
......@@ -15,4 +15,4 @@
.note-form-actions.clearfix
= f.submit 'Add Comment', class: "btn btn-nr btn-create comment-btn btn-grouped js-comment-button"
= yield(:note_actions)
%a.btn.btn-cancel.js-close-discussion-note-form Cancel
%a.btn.btn-nr.btn-cancel.js-close-discussion-note-form Cancel
......@@ -4,7 +4,7 @@
.col-sm-10
= users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true)
.help-block
Search for existing users or invite new ones using their email address.
Search for users by name, username, or email, or invite new ones using their email address.
.form-group
= f.label :access_level, "Project Access", class: 'control-label'
......
......@@ -11,7 +11,7 @@ class Settings < Settingslogic
# get host without www, thanks to http://stackoverflow.com/a/6674363/1233435
def get_host_without_www(url)
url = URI.encode(url)
url = CGI.escape(url)
uri = URI.parse(url)
uri = URI.parse("http://#{url}") if uri.scheme.nil?
host = uri.host.downcase
......
Haml::Template.options[:ugly] = true
# Remove the `:coffee` and `:coffeescript` filters
#
# See https://git.io/vztMu and http://stackoverflow.com/a/17571242/223897
Haml::Filters.remove_filter('coffee')
Haml::Filters.remove_filter('coffeescript')
class AddMetricsSampleInterval < ActiveRecord::Migration
def change
add_column :application_settings, :metrics_sample_interval, :integer,
default: 15
end
end
This diff is collapsed.
......@@ -70,6 +70,8 @@
## Contributor documentation
- [Documentation styleguide](development/doc_styleguide.md) Use this styleguide if you are
contributing to documentation.
- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
- [Legal](legal/README.md) Contributor license agreements.
- [Release](release/README.md) How to make the monthly and security releases.
......@@ -76,7 +76,10 @@ Parameters:
"updated_at": "2013-09-30T13: 46: 02Z"
},
"archived": false,
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png"
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0
},
{
"id": 6,
......@@ -129,7 +132,11 @@ Parameters:
}
},
"archived": false,
"avatar_url": null
"avatar_url": null,
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02"
}
]
```
......@@ -244,7 +251,11 @@ Parameters:
}
},
"archived": false,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png"
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
"runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
}
```
......
# Documentation styleguide
This styleguide recommends best practices to improve documentation and to keep
it organized and easy to find.
## Naming
- When creating a new document and it has more than one word in its name,
make sure to use underscores instead of spaces or dashes (`-`). For example,
a proper naming would be `import_projects_from_github.md`. The same rule
applies to images.
## Text
- Split up long lines, this makes it much easier to review and edit. Only
double line breaks are shown as a full line break in [GitLab markdown][gfm].
80-100 characters is a good line length
- Make sure that the documentation is added in the correct directory and that
there's a link to it somewhere useful
- Do not duplicate information
- Be brief and clear
- Unless there's a logical reason not to, add documents in alphabetical order
- Write in US English
- Use [single spaces][] instead of double spaces
## Formatting
- Use dashes (`-`) for unordered lists instead of asterisks (`*`)
- Use the number one (`1`) for ordered lists
- Use underscores (`_`) to mark a word or text in italics
- Use double asterisks (`**`) to mark a word or text in bold
- When using lists, prefer not to end each item with a period. You can use
them if there are multiple sentences, just keep the last sentence without
a period
## Headings
- Add only one H1 title in each document, by adding `#` at the beginning of
it (when using markdown). For subheadings, use `##`, `###` and so on
- Avoid putting numbers in headings. Numbers shift, hence documentation anchor
links shift too, which eventually leads to dead links. If you think it is
compelling to add numbers in headings, make sure to at least discuss it with
someone in the Merge Request
- When introducing a new document, be careful for the headings to be
grammatically and syntactically correct. It is advised to mention one or all
of the following GitLab members for a review: `@axil`, `@rspeicher`,
`@dblessing`, `@ashleys`, `@nearlythere`. This is to ensure that no document
with wrong heading is going live without an audit, thus preventing dead links
and redirection issues when corrected
- Leave exactly one newline after a heading
## Links
- If a link makes the paragraph to span across multiple lines, do not use
the regular Markdown approach: `[Text](https://example.com)`. Instead use
`[Text][identifier]` and at the very bottom of the document add:
`[identifier]: https://example.com`. This is another way to create Markdown
links which keeps the document clear and concise. Bonus points if you also
add an alternative text: `[identifier]: https://example.com "Alternative text"`
that appears when hovering your mouse on a link
## Images
- Place images in a separate directory named `img/` in the same directory where
the `.md` document that you're working on is located. Always prepend their
names with the name of the document that they will be included in. For
example, if there is a document called `twitter.md`, then a valid image name
could be `twitter_login_screen.png`.
- Images should have a specific, non-generic name that will differentiate them.
- Keep all file names in