diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py
--- a/rhodecode/config/routing.py
+++ b/rhodecode/config/routing.py
@@ -346,6 +346,14 @@ def make_map(config):
controller='changeset', revision='tip',
conditions=dict(function=check_repo))
+ rmap.connect('changeset_comment', '/{repo_name:.*}/changeset/{revision}/comment',
+ controller='changeset', revision='tip', action='comment',
+ conditions=dict(function=check_repo))
+
+ rmap.connect('changeset_comment_delete', '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
+ controller='changeset', action='delete_comment',
+ conditions=dict(function=check_repo))
+
rmap.connect('raw_changeset_home',
'/{repo_name:.*}/raw-changeset/{revision}',
controller='changeset', action='raw_changeset',
diff --git a/rhodecode/controllers/changeset.py b/rhodecode/controllers/changeset.py
--- a/rhodecode/controllers/changeset.py
+++ b/rhodecode/controllers/changeset.py
@@ -29,15 +29,19 @@ import traceback
from pylons import tmpl_context as c, url, request, response
from pylons.i18n.translation import _
from pylons.controllers.util import redirect
+from pylons.decorators import jsonify
import rhodecode.lib.helpers as h
-from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
+from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
+ NotAnonymous
from rhodecode.lib.base import BaseRepoController, render
from rhodecode.lib.utils import EmptyChangeset
from rhodecode.lib.compat import OrderedDict
+from rhodecode.model.db import ChangesetComment
+from rhodecode.model.comment import ChangesetCommentsModel
from vcs.exceptions import RepositoryError, ChangesetError, \
-ChangesetDoesNotExistError
+ ChangesetDoesNotExistError
from vcs.nodes import FileNode
from vcs.utils import diffs as differ
@@ -66,7 +70,7 @@ class ChangesetController(BaseRepoContro
#get ranges of revisions if preset
rev_range = revision.split('...')[:2]
-
+
try:
if len(rev_range) == 2:
rev_start = rev_range[0]
@@ -92,6 +96,14 @@ class ChangesetController(BaseRepoContro
c.lines_deleted = 0
c.cut_off = False # defines if cut off limit is reached
+ c.comments = []
+ for cs in c.cs_ranges:
+ c.comments.extend(ChangesetComment.query()\
+ .filter(ChangesetComment.repo_id == c.rhodecode_db_repo.repo_id)\
+ .filter(ChangesetComment.commit_id == cs.raw_id)\
+ .filter(ChangesetComment.line_no == None)\
+ .filter(ChangesetComment.f_path == None).all())
+
# Iterate over ranges (default changeset view is always one changeset)
for changeset in c.cs_ranges:
c.changes[changeset.raw_id] = []
@@ -252,3 +264,22 @@ class ChangesetController(BaseRepoContro
c.diffs += x[2]
return render('changeset/raw_changeset.html')
+
+ def comment(self, repo_name, revision):
+ ccmodel = ChangesetCommentsModel()
+
+ ccmodel.create(text=request.POST.get('text'),
+ repo_id=c.rhodecode_db_repo.repo_id,
+ user_id=c.rhodecode_user.user_id,
+ commit_id=revision, f_path=request.POST.get('f_path'),
+ line_no = request.POST.get('line'))
+
+ return redirect(h.url('changeset_home', repo_name=repo_name,
+ revision=revision))
+
+ @jsonify
+ @HasRepoPermissionAnyDecorator('hg.admin', 'repository.admin')
+ def delete_comment(self, comment_id):
+ ccmodel = ChangesetCommentsModel()
+ ccmodel.delete(comment_id=comment_id)
+ return True
diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py
--- a/rhodecode/lib/helpers.py
+++ b/rhodecode/lib/helpers.py
@@ -39,6 +39,8 @@ from vcs.utils.annotate import annotate_
from rhodecode.lib.utils import repo_name_slug
from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
+from rhodecode.lib.markup_renderer import MarkupRenderer
+
def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
"""
Reset button
@@ -531,7 +533,7 @@ class RepoPage(Page):
self.last_item = ((self.item_count - 1) - items_per_page *
(self.page - 1))
- self.items = list(self.collection[self.first_item:self.last_item+1])
+ self.items = list(self.collection[self.first_item:self.last_item + 1])
# Links to previous and next page
@@ -668,3 +670,6 @@ def urlify_text(text):
return literal(url_pat.sub(url_func, text))
+
+def rst(source):
+ return literal('
%s
' % MarkupRenderer.rst(source))
diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/model/comment.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+"""
+ rhodecode.model.comment
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ comments model for RhodeCode
+
+ :created_on: Nov 11, 2011
+ :author: marcink
+ :copyright: (C) 2009-2011 Marcin Kuzminski
+ :license: GPLv3, see COPYING for more details.
+"""
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+import logging
+import traceback
+
+from rhodecode.model import BaseModel
+from rhodecode.model.db import ChangesetComment
+
+log = logging.getLogger(__name__)
+
+
+class ChangesetCommentsModel(BaseModel):
+
+
+ def create(self, text, repo_id, user_id, commit_id, f_path=None,
+ line_no=None):
+ """
+ Creates new comment for changeset
+
+ :param text:
+ :param repo_id:
+ :param user_id:
+ :param commit_id:
+ :param f_path:
+ :param line_no:
+ """
+
+ comment = ChangesetComment()
+ comment.repo_id = repo_id
+ comment.user_id = user_id
+ comment.commit_id = commit_id
+ comment.text = text
+ comment.f_path = f_path
+ comment.line_no = line_no
+
+ self.sa.add(comment)
+ self.sa.commit()
+ return comment
+
+ def delete(self, comment_id):
+ """
+ Deletes given comment
+
+ :param comment_id:
+ """
+ comment = ChangesetComment.get(comment_id)
+ self.sa.delete(comment)
+ self.sa.commit()
+ return comment
diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py
--- a/rhodecode/model/db.py
+++ b/rhodecode/model/db.py
@@ -1095,6 +1095,22 @@ class CacheInvalidation(Base, BaseModel)
Session.commit()
+class ChangesetComment(Base, BaseModel):
+ __tablename__ = 'changeset_comments'
+ __table_args__ = ({'extend_existing':True},)
+ comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
+ repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+ commit_id = Column('commit_id', String(100), nullable=False)
+ line_no = Column('line_no', Integer(), nullable=True)
+ f_path = Column('f_path', String(1000), nullable=True)
+ user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+ text = Column('text', String(25000), nullable=False)
+ modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+ author = relationship('User')
+ repo = relationship('Repository')
+
+
class DbMigrateVersion(Base, BaseModel):
__tablename__ = 'db_migrate_version'
__table_args__ = {'extend_existing':True}
diff --git a/rhodecode/public/css/diff.css b/rhodecode/public/css/diff.css
--- a/rhodecode/public/css/diff.css
+++ b/rhodecode/public/css/diff.css
@@ -8,6 +8,11 @@ div.diffblock {
/* new */
line-height: 125%;
}
+
+div.diffblock.margined{
+ margin: 0px 20px 0px 20px;
+}
+
div.diffblock .code-header{
border-bottom: 1px solid #CCCCCC;
background: #EEEEEE;
diff --git a/rhodecode/public/css/style.css b/rhodecode/public/css/style.css
--- a/rhodecode/public/css/style.css
+++ b/rhodecode/public/css/style.css
@@ -3135,3 +3135,201 @@ div.readme .readme_box pre {
-moz-border-radius: 3px;
border-radius: 3px;
}
+
+
+/** RST STYLE **/
+
+
+div.rst-block {
+ padding:0px;
+}
+
+div.rst-block h2 {
+ font-weight: normal;
+}
+
+div.rst-block {
+ background-color: #fafafa;
+}
+
+div.rst-block {
+clear:both;
+overflow:hidden;
+margin:0;
+padding:0 20px 10px;
+}
+
+div.rst-block h1, div.rst-block h2, div.rst-block h3, div.rst-block h4, div.rst-block h5, div.rst-block h6 {
+border-bottom: 0 !important;
+margin: 0 !important;
+padding: 0 !important;
+line-height: 1.5em !important;
+}
+
+
+div.rst-block h1:first-child {
+padding-top: .25em !important;
+}
+
+div.rst-block h2, div.rst-block h3 {
+margin: 1em 0 !important;
+}
+
+div.rst-block h2 {
+margin-top: 1.5em !important;
+border-top: 4px solid #e0e0e0 !important;
+padding-top: .5em !important;
+}
+
+div.rst-block p {
+color: black !important;
+margin: 1em 0 !important;
+line-height: 1.5em !important;
+}
+
+div.rst-block ul {
+list-style: disc !important;
+margin: 1em 0 1em 2em !important;
+}
+
+div.rst-block ol {
+list-style: decimal;
+margin: 1em 0 1em 2em !important;
+}
+
+div.rst-block pre, code {
+font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
+}
+
+div.rst-block code {
+ font-size: 12px !important;
+ background-color: ghostWhite !important;
+ color: #444 !important;
+ padding: 0 .2em !important;
+ border: 1px solid #dedede !important;
+}
+
+div.rst-block pre code {
+ padding: 0 !important;
+ font-size: 12px !important;
+ background-color: #eee !important;
+ border: none !important;
+}
+
+div.rst-block pre {
+ margin: 1em 0;
+ font-size: 12px;
+ background-color: #eee;
+ border: 1px solid #ddd;
+ padding: 5px;
+ color: #444;
+ overflow: auto;
+ -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+
+
+.comments {
+ padding:10px 20px;
+}
+
+.comments .comment {
+ border: 1px solid #ddd;
+ margin-top: 10px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.comments .comment .meta {
+ background: #f8f8f8;
+ padding: 6px;
+ border-bottom: 1px solid #ddd;
+}
+
+.comments .comment .meta img {
+ vertical-align: middle;
+}
+
+.comments .comment .meta .user {
+ font-weight: bold;
+}
+
+.comments .comment .meta .date {
+ float: right;
+}
+
+.comments .comment .text {
+ margin-top: 7px;
+ padding: 6px;
+ padding-bottom: 13px;
+}
+
+.comments .comments-number{
+ padding:0px 0px 10px 0px;
+ font-weight: bold;
+}
+
+.comment-form .clearfix{
+ background: #EEE;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ padding: 10px;
+}
+
+div.comment-form {
+ margin-top: 20px;
+}
+
+.comment-form strong {
+ display: block;
+ margin-bottom: 15px;
+}
+
+.comment-form textarea {
+ width: 100%;
+ height: 100px;
+ font-family: 'Monaco', 'Courier', 'Courier New', monospace;
+}
+
+form.comment-form {
+ margin-top: 10px;
+ margin-left: 10px;
+}
+
+.comment-form-submit {
+ margin-top: 5px;
+ margin-left: 525px;
+}
+
+.file-comments {
+ display: none;
+}
+
+.comment-form .comment {
+ margin-left: 10px;
+}
+
+.comment-form .comment-help{
+ padding: 0px 0px 5px 0px;
+ color: #666;
+}
+
+.comment-form .comment-button{
+ padding-top:5px;
+}
+
+.add-another-button {
+ margin-left: 10px;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+.comment .buttons {
+ float: right;
+ display: none;
+}
diff --git a/rhodecode/templates/changeset/changeset.html b/rhodecode/templates/changeset/changeset.html
--- a/rhodecode/templates/changeset/changeset.html
+++ b/rhodecode/templates/changeset/changeset.html
@@ -108,7 +108,7 @@
%for change,filenode,diff,cs1,cs2,stat in c.changes:
%if change !='removed':
-
+
%endif
- %endfor
-
+ %endfor
+
+ <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
+
+
+
+
%def>
diff --git a/rhodecode/templates/changeset/changeset_file_comment.html b/rhodecode/templates/changeset/changeset_file_comment.html
new file mode 100644
--- /dev/null
+++ b/rhodecode/templates/changeset/changeset_file_comment.html
@@ -0,0 +1,31 @@
+##usage:
+## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
+## ${comment.comment_block(co)}
+##
+<%def name="comment_block(co)">
+
+%def>
\ No newline at end of file