diff --git a/rhodecode/__init__.py b/rhodecode/__init__.py
--- a/rhodecode/__init__.py
+++ b/rhodecode/__init__.py
@@ -48,7 +48,7 @@ PYRAMID_SETTINGS = {}
EXTENSIONS = {}
__version__ = ('.'.join((str(each) for each in VERSION[:3])))
-__dbversion__ = 105 # defines current db version for migrations
+__dbversion__ = 106 # defines current db version for migrations
__platform__ = platform.system()
__license__ = 'AGPLv3, and Commercial License'
__author__ = 'RhodeCode GmbH'
diff --git a/rhodecode/apps/debug_style/views.py b/rhodecode/apps/debug_style/views.py
--- a/rhodecode/apps/debug_style/views.py
+++ b/rhodecode/apps/debug_style/views.py
@@ -220,7 +220,7 @@ def db():
'pr_comment_url': 'http://comment-url',
'pr_comment_reply_url': 'http://comment-url#reply',
- 'comment_file': 'rhodecode/model/db.py',
+ 'comment_file': 'rhodecode/model/get_flow_commits',
'comment_line': 'o1210',
'comment_type': 'todo',
'comment_body': '''
diff --git a/rhodecode/apps/repository/tests/test_repo_commit_comments.py b/rhodecode/apps/repository/tests/test_repo_commit_comments.py
--- a/rhodecode/apps/repository/tests/test_repo_commit_comments.py
+++ b/rhodecode/apps/repository/tests/test_repo_commit_comments.py
@@ -22,8 +22,7 @@ import pytest
from rhodecode.tests import TestController
-from rhodecode.model.db import (
- ChangesetComment, Notification, UserNotification)
+from rhodecode.model.db import ChangesetComment, Notification
from rhodecode.model.meta import Session
from rhodecode.lib import helpers as h
@@ -269,7 +268,36 @@ class TestRepoCommitCommentsView(TestCon
repo_name=backend.repo_name, commit_id=commit_id))
assert_comment_links(response, 0, 0)
- @pytest.mark.parametrize('renderer, input, output', [
+ def test_delete_forbidden_for_immutable_comments(self, backend):
+ self.log_user()
+ commit_id = backend.repo.get_commit('300').raw_id
+ text = u'CommentOnCommit'
+
+ params = {'text': text, 'csrf_token': self.csrf_token}
+ self.app.post(
+ route_path(
+ 'repo_commit_comment_create',
+ repo_name=backend.repo_name, commit_id=commit_id),
+ params=params)
+
+ comments = ChangesetComment.query().all()
+ assert len(comments) == 1
+ comment_id = comments[0].comment_id
+
+ comment = ChangesetComment.get(comment_id)
+ comment.immutable_state = ChangesetComment.OP_IMMUTABLE
+ Session().add(comment)
+ Session().commit()
+
+ self.app.post(
+ route_path('repo_commit_comment_delete',
+ repo_name=backend.repo_name,
+ commit_id=commit_id,
+ comment_id=comment_id),
+ params={'csrf_token': self.csrf_token},
+ status=403)
+
+ @pytest.mark.parametrize('renderer, text_input, output', [
('rst', 'plain text', '
plain text
'),
('rst', 'header\n======', 'header
'),
('rst', '*italics*', 'italics'),
@@ -280,11 +308,11 @@ class TestRepoCommitCommentsView(TestCon
('markdown', '**bold**', 'bold'),
], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain',
'md-header', 'md-italics', 'md-bold', ])
- def test_preview(self, renderer, input, output, backend, xhr_header):
+ def test_preview(self, renderer, text_input, output, backend, xhr_header):
self.log_user()
params = {
'renderer': renderer,
- 'text': input,
+ 'text': text_input,
'csrf_token': self.csrf_token
}
commit_id = '0' * 16 # fake this for tests
diff --git a/rhodecode/apps/repository/views/repo_commits.py b/rhodecode/apps/repository/views/repo_commits.py
--- a/rhodecode/apps/repository/views/repo_commits.py
+++ b/rhodecode/apps/repository/views/repo_commits.py
@@ -22,7 +22,7 @@
import logging
import collections
-from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
+from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden
from pyramid.view import view_config
from pyramid.renderers import render
from pyramid.response import Response
@@ -538,6 +538,10 @@ class RepoCommitsView(RepoAppView):
# comment already deleted in another call probably
return True
+ if comment.immutable:
+ # don't allow deleting comments that are immutable
+ raise HTTPForbidden()
+
is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
super_admin = h.HasPermissionAny('hg.admin')()
comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
diff --git a/rhodecode/apps/repository/views/repo_pull_requests.py b/rhodecode/apps/repository/views/repo_pull_requests.py
--- a/rhodecode/apps/repository/views/repo_pull_requests.py
+++ b/rhodecode/apps/repository/views/repo_pull_requests.py
@@ -1473,6 +1473,10 @@ class RepoPullRequestsView(RepoAppView,
self.request.matchdict['comment_id'])
comment_id = comment.comment_id
+ if comment.immutable:
+ # don't allow deleting comments that are immutable
+ raise HTTPForbidden()
+
if pull_request.is_closed():
log.debug('comment: forbidden because pull request is closed')
raise HTTPForbidden()
diff --git a/rhodecode/lib/dbmigrate/versions/106_version_4_19_0.py b/rhodecode/lib/dbmigrate/versions/106_version_4_19_0.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/lib/dbmigrate/versions/106_version_4_19_0.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import logging
+from sqlalchemy import *
+
+from alembic.migration import MigrationContext
+from alembic.operations import Operations
+from sqlalchemy import BigInteger
+
+from rhodecode.lib.dbmigrate.versions import _reset_base
+from rhodecode.model import init_model_encryption
+
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+ """
+ Upgrade operations go here.
+ Don't create your own engine; bind migrate_engine to your metadata
+ """
+ _reset_base(migrate_engine)
+ from rhodecode.lib.dbmigrate.schema import db_4_18_0_1 as db
+
+ init_model_encryption(db)
+
+ context = MigrationContext.configure(migrate_engine.connect())
+ op = Operations(context)
+
+ comments = db.ChangesetComment.__table__
+
+ with op.batch_alter_table(comments.name) as batch_op:
+ new_column = Column('immutable_state', Unicode(128), nullable=True)
+ batch_op.add_column(new_column)
+
+
+def downgrade(migrate_engine):
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+
+def fixups(models, _SESSION):
+ pass
diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py
--- a/rhodecode/model/db.py
+++ b/rhodecode/model/db.py
@@ -3711,6 +3711,9 @@ class ChangesetComment(Base, BaseModel):
COMMENT_TYPE_TODO = u'todo'
COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
+ OP_IMMUTABLE = u'immutable'
+ OP_CHANGEABLE = u'changeable'
+
comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
revision = Column('revision', String(40), nullable=True)
@@ -3725,6 +3728,7 @@ class ChangesetComment(Base, BaseModel):
modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
renderer = Column('renderer', Unicode(64), nullable=True)
display_state = Column('display_state', Unicode(128), nullable=True)
+ immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
@@ -3767,6 +3771,10 @@ class ChangesetComment(Base, BaseModel):
def outdated(self):
return self.display_state == self.COMMENT_OUTDATED
+ @property
+ def immutable(self):
+ return self.immutable_state == self.OP_IMMUTABLE
+
def outdated_at_version(self, version):
"""
Checks if comment is outdated for given pull request version
diff --git a/rhodecode/templates/changeset/changeset_file_comment.mako b/rhodecode/templates/changeset/changeset_file_comment.mako
--- a/rhodecode/templates/changeset/changeset_file_comment.mako
+++ b/rhodecode/templates/changeset/changeset_file_comment.mako
@@ -135,7 +135,7 @@
## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
%if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
## permissions to delete
- %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
+ %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
## TODO: dan: add edit comment here
%else: