Commit fad71576 authored by Stan Hu's avatar Stan Hu
Browse files

Fix cross references when usernames, milestones, or project names contain underscores.

Remove emphasis from system notes to avoid Markdown conflicts in names.
parent 6c1074e3
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 7.10.0 (unreleased) v 7.10.0 (unreleased)
- Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu)
- enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger) - enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger)
- extend the commit calendar to show the actual commits made on a date (Hannes Rosenögger) - extend the commit calendar to show the actual commits made on a date (Hannes Rosenögger)
- Add a service to support external wikis (Hannes Rosenögger) - Add a service to support external wikis (Hannes Rosenögger)
......
...@@ -59,7 +59,7 @@ class Note < ActiveRecord::Base ...@@ -59,7 +59,7 @@ class Note < ActiveRecord::Base
class << self class << self
def create_status_change_note(noteable, project, author, status, source) def create_status_change_note(noteable, project, author, status, source)
body = "_Status changed to #{status}#{' by ' + source.gfm_reference if source}_" body = "Status changed to #{status}#{' by ' + source.gfm_reference if source}"
create( create(
noteable: noteable, noteable: noteable,
...@@ -95,9 +95,9 @@ def create_cross_reference_note(noteable, mentioner, author, project) ...@@ -95,9 +95,9 @@ def create_cross_reference_note(noteable, mentioner, author, project)
def create_milestone_change_note(noteable, project, author, milestone) def create_milestone_change_note(noteable, project, author, milestone)
body = if milestone.nil? body = if milestone.nil?
'_Milestone removed_' 'Milestone removed'
else else
"_Milestone changed to #{milestone.title}_" "Milestone changed to #{milestone.title}"
end end
create( create(
...@@ -110,7 +110,7 @@ def create_milestone_change_note(noteable, project, author, milestone) ...@@ -110,7 +110,7 @@ def create_milestone_change_note(noteable, project, author, milestone)
end end
def create_assignee_change_note(noteable, project, author, assignee) def create_assignee_change_note(noteable, project, author, assignee)
body = assignee.nil? ? '_Assignee removed_' : "_Reassigned to @#{assignee.username}_" body = assignee.nil? ? 'Assignee removed' : "Reassigned to @#{assignee.username}"
create({ create({
noteable: noteable, noteable: noteable,
...@@ -140,7 +140,7 @@ def create_labels_change_note(noteable, project, author, added_labels, removed_l ...@@ -140,7 +140,7 @@ def create_labels_change_note(noteable, project, author, added_labels, removed_l
end end
message << ' ' << 'label'.pluralize(labels_count) message << ' ' << 'label'.pluralize(labels_count)
body = "_#{message.capitalize}_" body = "#{message.capitalize}"
create( create(
noteable: noteable, noteable: noteable,
...@@ -170,14 +170,14 @@ def create_new_commits_note(merge_request, project, author, new_commits, existin ...@@ -170,14 +170,14 @@ def create_new_commits_note(merge_request, project, author, new_commits, existin
commits_text = ActionController::Base.helpers.pluralize(existing_commits.length, 'commit') commits_text = ActionController::Base.helpers.pluralize(existing_commits.length, 'commit')
branch = branch =
if merge_request.for_fork? if merge_request.for_fork?
"#{merge_request.target_project_namespace}:#{merge_request.target_branch}" "#{merge_request.target_project_namespace}:#{merge_request.target_branch}"
else else
merge_request.target_branch merge_request.target_branch
end end
message = "* #{commit_ids} - _#{commits_text} from branch `#{branch}`_" message = "* #{commit_ids} - #{commits_text} from branch `#{branch}`"
body << message body << message
body << "\n" body << "\n"
end end
...@@ -240,7 +240,7 @@ def cross_reference_exists?(noteable, mentioner) ...@@ -240,7 +240,7 @@ def cross_reference_exists?(noteable, mentioner)
where(noteable_id: noteable.id) where(noteable_id: noteable.id)
end end
notes.where('note like ?', cross_reference_note_content(gfm_reference)). notes.where('note like ?', cross_reference_note_pattern(gfm_reference)).
system.any? system.any?
end end
...@@ -249,13 +249,18 @@ def search(query) ...@@ -249,13 +249,18 @@ def search(query)
end end
def cross_reference_note_prefix def cross_reference_note_prefix
'_mentioned in ' 'mentioned in '
end end
private private
def cross_reference_note_content(gfm_reference) def cross_reference_note_content(gfm_reference)
cross_reference_note_prefix + "#{gfm_reference}_" cross_reference_note_prefix + "#{gfm_reference}"
end
def cross_reference_note_pattern(gfm_reference)
# Older cross reference notes contained underscores for emphasis
"%" + cross_reference_note_content(gfm_reference) + "%"
end end
# Prepend the mentioner's namespaced project path to the GFM reference for # Prepend the mentioner's namespaced project path to the GFM reference for
......
...@@ -120,7 +120,7 @@ def new_note(note) ...@@ -120,7 +120,7 @@ def new_note(note)
return true unless note.noteable_type.present? return true unless note.noteable_type.present?
# ignore gitlab service messages # ignore gitlab service messages
return true if note.note.start_with?('_Status changed to closed_') return true if note.note.start_with?('Status changed to closed')
return true if note.cross_reference? && note.system == true return true if note.cross_reference? && note.system == true
opts = { noteable_type: note.noteable_type, project_id: note.project_id } opts = { noteable_type: note.noteable_type, project_id: note.project_id }
......
...@@ -375,7 +375,7 @@ Parameters: ...@@ -375,7 +375,7 @@ Parameters:
} }
}, },
{ {
"note": "_Status changed to closed_", "note": "Status changed to closed",
"author": { "author": {
"id": 11, "id": 11,
"username": "admin", "username": "admin",
......
...@@ -21,7 +21,7 @@ Parameters: ...@@ -21,7 +21,7 @@ Parameters:
[ [
{ {
"id": 302, "id": 302,
"body": "_Status changed to closed_", "body": "Status changed to closed",
"attachment": null, "attachment": null,
"author": { "author": {
"id": 1, "id": 1,
......
...@@ -120,4 +120,18 @@ ...@@ -120,4 +120,18 @@
expect(extracted[0][1].message).to eq(commit.message) expect(extracted[0][1].message).to eq(commit.message)
end end
end end
context 'with a project with an underscore' do
let(:project) { create(:project, path: 'test_project') }
let(:issue) { create(:issue, project: project) }
it 'handles project issue references' do
subject.analyze("this refers issue #{project.path_with_namespace}##{issue.iid}",
project)
extracted = subject.issues_for(project)
expect(extracted.size).to eq(1)
expect(extracted).to eq([issue])
end
end
end end
...@@ -182,14 +182,14 @@ ...@@ -182,14 +182,14 @@
describe '#note' do describe '#note' do
subject { super().note } subject { super().note }
it { is_expected.to match(/Status changed to #{status}/) } it { is_expected.to eq("Status changed to #{status}") }
end end
it 'appends a back-reference if a closing mentionable is supplied' do it 'appends a back-reference if a closing mentionable is supplied' do
commit = double('commit', gfm_reference: 'commit 123456') commit = double('commit', gfm_reference: 'commit 123456')
n = Note.create_status_change_note(thing, project, author, status, commit) n = Note.create_status_change_note(thing, project, author, status, commit)
expect(n.note).to match(/Status changed to #{status} by commit 123456/) expect(n.note).to eq("Status changed to #{status} by commit 123456")
end end
end end
...@@ -197,7 +197,7 @@ ...@@ -197,7 +197,7 @@
let(:project) { create(:project) } let(:project) { create(:project) }
let(:thing) { create(:issue, project: project) } let(:thing) { create(:issue, project: project) }
let(:author) { create(:user) } let(:author) { create(:user) }
let(:assignee) { create(:user) } let(:assignee) { create(:user, username: "assigned_user") }
subject { Note.create_assignee_change_note(thing, project, author, assignee) } subject { Note.create_assignee_change_note(thing, project, author, assignee) }
...@@ -227,7 +227,7 @@ ...@@ -227,7 +227,7 @@
describe '#note' do describe '#note' do
subject { super().note } subject { super().note }
it { is_expected.to match(/Reassigned to @#{assignee.username}/) } it { is_expected.to eq('Reassigned to @assigned_user') }
end end
context 'assignee is removed' do context 'assignee is removed' do
...@@ -235,11 +235,95 @@ ...@@ -235,11 +235,95 @@
describe '#note' do describe '#note' do
subject { super().note } subject { super().note }
it { is_expected.to match(/Assignee removed/) } it { is_expected.to eq('Assignee removed') }
end end
end end
end end
describe '#create_labels_change_note' do
let(:project) { create(:project) }
let(:thing) { create(:issue, project: project) }
let(:author) { create(:user) }
let(:label1) { create(:label) }
let(:label2) { create(:label) }
let(:added_labels) { [label1, label2] }
let(:removed_labels) { [] }
subject { Note.create_labels_change_note(thing, project, author, added_labels, removed_labels) }
context 'creates and saves a Note' do
it { is_expected.to be_a Note }
describe '#id' do
subject { super().id }
it { is_expected.not_to be_nil }
end
end
describe '#noteable' do
subject { super().noteable }
it { is_expected.to eq(thing) }
end
describe '#project' do
subject { super().project }
it { is_expected.to eq(thing.project) }
end
describe '#author' do
subject { super().author }
it { is_expected.to eq(author) }
end
describe '#note' do
subject { super().note }
it { is_expected.to eq("Added ~#{label1.id} ~#{label2.id} labels") }
end
context 'label is removed' do
let(:added_labels) { [label1] }
let(:removed_labels) { [label2] }
describe '#note' do
subject { super().note }
it { is_expected.to eq("Added ~#{label1.id} and removed ~#{label2.id} labels") }
end
end
end
describe '#create_milestone_change_note' do
let(:project) { create(:project) }
let(:thing) { create(:issue, project: project) }
let(:milestone) { create(:milestone, project: project, title: "first_milestone") }
let(:author) { create(:user) }
subject { Note.create_milestone_change_note(thing, project, author, milestone) }
context 'creates and saves a Note' do
it { is_expected.to be_a Note }
describe '#id' do
subject { super().id }
it { is_expected.not_to be_nil }
end
end
describe '#project' do
subject { super().project }
it { is_expected.to eq(thing.project) }
end
describe '#author' do
subject { super().author }
it { is_expected.to eq(author) }
end
describe '#note' do
subject { super().note }
it { is_expected.to eq("Milestone changed to first_milestone") }
end
end
describe '#create_cross_reference_note' do describe '#create_cross_reference_note' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:author) { create(:user) } let(:author) { create(:user) }
...@@ -272,7 +356,7 @@ ...@@ -272,7 +356,7 @@
describe '#note' do describe '#note' do
subject { super().note } subject { super().note }
it { is_expected.to eq("_mentioned in merge request !#{mergereq.iid}_") } it { is_expected.to eq("mentioned in merge request !#{mergereq.iid}") }
end end
end end
...@@ -288,7 +372,7 @@ ...@@ -288,7 +372,7 @@
describe '#note' do describe '#note' do
subject { super().note } subject { super().note }
it { is_expected.to eq("_mentioned in commit #{commit.sha}_") } it { is_expected.to eq("mentioned in commit #{commit.sha}") }
end end
end end
...@@ -309,7 +393,7 @@ ...@@ -309,7 +393,7 @@
describe '#note' do describe '#note' do
subject { super().note } subject { super().note }
it { is_expected.to eq("_mentioned in issue ##{issue.iid}_") } it { is_expected.to eq("mentioned in issue ##{issue.iid}") }
end end
end end
...@@ -330,7 +414,7 @@ ...@@ -330,7 +414,7 @@
describe '#note' do describe '#note' do
subject { super().note } subject { super().note }
it { is_expected.to eq("_mentioned in merge request !#{mergereq.iid}_") } it { is_expected.to eq("mentioned in merge request !#{mergereq.iid}") }
end end
end end
...@@ -362,7 +446,7 @@ ...@@ -362,7 +446,7 @@
describe '#note' do describe '#note' do
subject { super().note } subject { super().note }
it { is_expected.to eq("_mentioned in issue ##{issue.iid}_") } it { is_expected.to eq("mentioned in issue ##{issue.iid}") }
end end
end end
...@@ -389,7 +473,7 @@ ...@@ -389,7 +473,7 @@
describe '#note' do describe '#note' do
subject { super().note } subject { super().note }
it { is_expected.to eq("_mentioned in commit #{parent_commit.id}_") } it { is_expected.to eq("mentioned in commit #{parent_commit.id}") }
end end
end end
end end
...@@ -421,6 +505,41 @@ ...@@ -421,6 +505,41 @@
it { expect(Note.cross_reference_exists?(commit0, commit1)).to be_truthy } it { expect(Note.cross_reference_exists?(commit0, commit1)).to be_truthy }
it { expect(Note.cross_reference_exists?(commit1, commit0)).to be_falsey } it { expect(Note.cross_reference_exists?(commit1, commit0)).to be_falsey }
end end
context 'legacy note with Markdown emphasis' do
let(:issue2) { create :issue, project: project }
let!(:note) do
create :note, system: true, noteable_id: issue2.id,
noteable_type: "Issue", note: "_mentioned in issue " \
"#{issue.project.path_with_namespace}##{issue.iid}_"
end
it 'detects if a mentionable with emphasis has been mentioned' do
expect(Note.cross_reference_exists?(issue2, issue)).to be_truthy
end
end
end
describe '#cross_references_with_underscores?' do
let(:project) { create :project, path: "first_project" }
let(:second_project) { create :project, path: "second_project" }
let(:author) { create :user }
let(:issue0) { create :issue, project: project }
let(:issue1) { create :issue, project: second_project }
let!(:note) { Note.create_cross_reference_note(issue0, issue1, author, project) }
it 'detects if a mentionable has already been mentioned' do
expect(Note.cross_reference_exists?(issue0, issue1)).to be_truthy
end
it 'detects if a mentionable has not already been mentioned' do
expect(Note.cross_reference_exists?(issue1, issue0)).to be_falsey
end
it 'detects that text has underscores' do
expect(note.note).to eq("mentioned in issue #{second_project.path_with_namespace}##{issue1.iid}")
end
end end
describe '#system?' do describe '#system?' do
...@@ -429,6 +548,8 @@ ...@@ -429,6 +548,8 @@
let(:other) { create(:issue, project: project) } let(:other) { create(:issue, project: project) }
let(:author) { create(:user) } let(:author) { create(:user) }
let(:assignee) { create(:user) } let(:assignee) { create(:user) }
let(:label) { create(:label) }
let(:milestone) { create(:milestone) }
it 'should recognize user-supplied notes as non-system' do it 'should recognize user-supplied notes as non-system' do
@note = create(:note_on_issue) @note = create(:note_on_issue)
...@@ -449,6 +570,16 @@ ...@@ -449,6 +570,16 @@
@note = Note.create_assignee_change_note(issue, project, author, assignee) @note = Note.create_assignee_change_note(issue, project, author, assignee)
expect(@note).to be_system expect(@note).to be_system
end end
it 'should identify label-change notes as system notes' do
@note = Note.create_labels_change_note(issue, project, author, [label], [])
expect(@note).to be_system
end
it 'should identify milestone-change notes as system notes' do
@note = Note.create_milestone_change_note(issue, project, author, milestone)
expect(@note).to be_system
end
end end
describe :authorization do describe :authorization do
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment