diff --git a/code_comments/comment.py b/code_comments/comment.py index 8850db9..428e380 100644 --- a/code_comments/comment.py +++ b/code_comments/comment.py @@ -71,10 +71,10 @@ def validate(self): def href(self): if self.is_comment_to_file: - href = self.req.href.browser(self.path, rev=self.revision, + href = self.req.href.browser(self.reponame + '/' + self.path, rev=self.revision, codecomment=self.id) elif self.is_comment_to_changeset: - href = self.req.href.changeset(self.revision, codecomment=self.id) + href = self.req.href.changeset(self.revision + '/' + self.reponame, codecomment=self.id) elif self.is_comment_to_attachment: href = self.req.href('/attachment/ticket/%d/%s' % (self.attachment_ticket, diff --git a/code_comments/comments.py b/code_comments/comments.py index b8282e9..574202f 100644 --- a/code_comments/comments.py +++ b/code_comments/comments.py @@ -13,7 +13,7 @@ class Comments(object): def __init__(self, req, env): self.req, self.env = req, env - self.valid_sorting_methods = ('id', 'author', 'time', 'path', 'text') + self.valid_sorting_methods = ('id', 'author', 'time', 'reponame', 'path', 'text') def comment_from_row(self, row): return Comment(self.req, self.env, row) @@ -21,10 +21,15 @@ def comment_from_row(self, row): def get_filter_values(self): comments = self.all() return { + 'repos': self.get_all_repos(comments), 'paths': self.get_all_paths(comments), 'authors': self.get_all_comment_authors(comments), } + def get_all_repos(self, comments): + # Skip the empty string which is the repository for comments on attachments + return sorted(list(set([comment.reponame for comment in comments if comment.reponame != '']))) + def get_all_paths(self, comments): def get_directory(path): parts = os.path.split(path)[0].split('/') @@ -96,6 +101,9 @@ def get_condition_str_and_corresponding_values(self, args): elif name.endswith('__lt'): name = name.replace('__lt', '') conditions.append(name + ' < %s') + elif name.endswith('__ne'): + name = name.replace('__ne', '') + conditions.append(name + ' != %s') elif name.endswith('__prefix'): values.append( args[name].replace('%', '\\%').replace('_', '\\_') + '%') diff --git a/code_comments/db.py b/code_comments/db.py index 0fb56a2..1b1bf77 100644 --- a/code_comments/db.py +++ b/code_comments/db.py @@ -4,9 +4,10 @@ from trac.db.schema import Table, Column, Index from trac.env import IEnvironmentSetupParticipant from trac.db.api import DatabaseManager +from trac.versioncontrol.api import RepositoryManager # Database version identifier for upgrades. -db_version = 3 +db_version = 5 db_version_key = 'code_comments_schema_version' # Database schema @@ -21,8 +22,11 @@ Column('author'), Column('time', type='int'), Column('type'), + Column('reponame'), Index(['path']), Index(['author']), + Index(['reponame', 'path']), + Index(['revision', 'reponame']), ], 'code_comments_subscriptions': Table('code_comments_subscriptions', key=('id', 'user', 'type', 'path', @@ -76,9 +80,61 @@ def upgrade_from_2_to_3(env): dbm.create_tables((schema['code_comments_subscriptions'],)) +def upgrade_from_3_to_4(env): + with env.db_transaction as db: + # Add the new column "reponame" and indexes. + db('ALTER TABLE code_comments ADD COLUMN reponame text') + db('CREATE INDEX code_comments_reponame_path_idx ON code_comments (reponame, path)') + db('CREATE INDEX code_comments_revision_reponame_idx ON code_comments (revision, reponame)') + + # Comments on attachments need to have the empty string as the reponame instead of NULL. + db("UPDATE code_comments SET reponame = '' WHERE type = 'attachment'") + + # Comments on changesets have the reponame in the 'path' column. + db("UPDATE code_comments SET reponame = path, path = '' WHERE type = 'changeset'") + + # Comments on files have the reponame as the first component of the 'path' column. + db(""" + UPDATE code_comments + SET + reponame = substr(path, 1, instr(path, '/') - 1), + path = substr(path, instr(path, '/') + 1) + WHERE + type = 'browser' + """) + + +def upgrade_from_4_to_5(env): + with env.db_transaction as db: + # The line numbers of all present comments on changesets are bogus, + # see https://github.com/trac-hacks/trac-code-comments-plugin/issues/67 + # We therefore set them to 0 detaching the comment from the line. We leave a note in + # the text of the comment explaining this. + + notice = '\n\nThis comment was created by a previous version of the '\ + "code-comments plugin and '''is not properly attached to a line of code'''. "\ + 'See [https://github.com/trac-hacks/trac-code-comments-plugin/issues/67 '\ + 'issue #67].\n\nThe comment was originally placed on line $oldLineNumber$ of '\ + 'the diff as it was displayed when the comment was created.' + notice = notice.replace("'", "''") + sql = """ + UPDATE code_comments + SET + line = 0, + text = text || REPLACE('{0}', '$oldLineNumber$', line) + WHERE + type = 'changeset' + AND + line != 0 + """ + db(sql.format(notice)) + + upgrade_map = { 2: upgrade_from_1_to_2, 3: upgrade_from_2_to_3, + 4: upgrade_from_3_to_4, + 5: upgrade_from_4_to_5 } diff --git a/code_comments/htdocs/code-comments.js b/code_comments/htdocs/code-comments.js index fe1cda6..8b2b734 100644 --- a/code_comments/htdocs/code-comments.js +++ b/code_comments/htdocs/code-comments.js @@ -28,6 +28,7 @@ var underscore = _.noConflict(); }, defaultFetchParams: { path: CodeComments.path || undefined, + reponame: CodeComments.reponame, revision: CodeComments.revision, type: CodeComments.page, }, @@ -35,7 +36,7 @@ var underscore = _.noConflict(); return this.fetch( { data: _.extend( { line: 0 }, this.defaultFetchParams ) } ); }, fetchLineComments: function() { - return this.fetch( { data: _.extend( { line__gt: 0 }, this.defaultFetchParams ) } ); + return this.fetch( { data: _.extend( { line__ne: 0 }, this.defaultFetchParams ) } ); } }); @@ -111,13 +112,15 @@ var underscore = _.noConflict(); }, addOne: function(comment) { var line = comment.get('line'); - if (!this.viewPerLine[line]) { - this.viewPerLine[line] = new CommentsForALineView( { line: line } ); + var file = comment.get('path'); + var key = 'file_' + file + ':' + line; + if (!this.viewPerLine[key]) { + this.viewPerLine[key] = new CommentsForALineView( { file: file, line: line } ); - var $tr = $( Rows.getTrByLineNumber( line ) ); - $tr.after(this.viewPerLine[line].render().el).addClass('with-comments'); + var $tr = $( Rows.getTrByFileAndLineNumberInFile( file, line ) ); + $tr.after(this.viewPerLine[key].render().el).addClass('with-comments'); } - this.viewPerLine[line].addOne(comment); + this.viewPerLine[key].addOne(comment); }, addAll: function() { var view = this; @@ -133,6 +136,7 @@ var underscore = _.noConflict(); template: _.template(CodeComments.templates.comments_for_a_line), initialize: function(attrs) { this.line = attrs.line; + this.file = attrs.file; }, events: { 'click button': 'showAddCommentDialog' @@ -217,6 +221,7 @@ var underscore = _.noConflict(); text: text, author: CodeComments.username, path: this.path, + reponame: CodeComments.reponame, revision: CodeComments.revision, line: line, type: CodeComments.page @@ -237,7 +242,7 @@ var underscore = _.noConflict(); var callbackMouseover = function( event ) { var row = new RowView( { el: this } ), file = row.getFile(), - line = row.getLineNumber(), + line = row.getLineNumberInFile(), displayLine = row.getDisplayLine(); row.replaceLineNumberCellContent( '' ); @@ -266,11 +271,33 @@ var underscore = _.noConflict(); // wrap TH content in spans so we can hide/show them this.wrapTHsInSpans(); }, - getLineByTR: function( tr ) { - return $.inArray( tr, this.$rows ) + 1; - }, - getTrByLineNumber: function( line ) { - return this.$rows[line - 1]; + getTrByFileAndLineNumberInFile: function ( file, line) { + var col; + var container; + if (CodeComments.page == "browser" && CodeComments.path == file) { + container = $( 'thead', 'table.code' ); + col = 0; + } else { + if (line < 0) { + line = -line; + col = 0; + } else { + col = 1 + } + var containers = $( 'thead', 'table.code, table.trac-diff' ); + for ( var i = 0; (container = containers[i]) != null; i++ ) { + if ($(container).parents( 'li' ).find( 'h2>a:first' ).text() == file) { + break; + } + } + } + var trs = $(container).parents( 'table' ).find( 'tbody>tr' ).not('.comments'); + for ( var i = 0, tr; (tr = trs[i]) != null; i++ ) { + if ($(tr).find( 'th>span' )[col].textContent == line) { + return tr; + } + } + return null; }, wrapTHsInSpans: function() { $( 'th', this.$rows ).each( function( i, elem ) { @@ -301,11 +328,19 @@ var underscore = _.noConflict(); getFile: function() { return this.$el.parents( 'li' ).find( 'h2>a:first' ).text(); }, - getLineNumber: function() { - return Rows.getLineByTR( this.el ); + getLineNumberInFile: function() { + var lineNumber = this.$lineNumberCell.text().trim(); + if (lineNumber) + return lineNumber; + else + return this.$th.first().text().trim() * -1; }, getDisplayLine: function() { - return this.$lineNumberCell.text().trim() || this.$th.first().text() + ' (deleted)'; + var lineNumber = this.getLineNumberInFile() + if (lineNumber > 0) + return lineNumber; + else + return lineNumber * -1 + ' (deleted)'; } } ); diff --git a/code_comments/subscription.py b/code_comments/subscription.py index cbe14dc..62342ff 100644 --- a/code_comments/subscription.py +++ b/code_comments/subscription.py @@ -236,13 +236,13 @@ def from_comment(cls, env, comment, user=None, notify=True): # Munge changesets and browser if comment.type in ('changeset', 'browser'): - rm = RepositoryManager(env) - reponame, repos, path = rm.get_repository_by_path(comment.path) if comment.type == 'browser': - sub['path'] = path + sub['path'] = comment.path else: sub['path'] = '' - sub['repos'] = reponame or '(default)' + sub['repos'] = comment.reponame + rm = RepositoryManager(env) + repos = rm.get_repository(comment.reponame) try: _cs = repos.get_changeset(comment.revision) except NoSuchChangeset: @@ -296,11 +296,9 @@ def for_comment(cls, env, comment, notify=None): args['rev'] = str(comment.revision) if comment.type == 'browser': - rm = RepositoryManager(env) - reponame, _, path = rm.get_repository_by_path(comment.path) args['type'] = ('browser', 'changeset') - args['path'] = (path, '') - args['repos'] = reponame + args['path'] = (comment.path, '') + args['repos'] = comment.reponame args['rev'] = (str(comment.revision), '') return cls.select(env, args, notify) diff --git a/code_comments/templates/comments.html b/code_comments/templates/comments.html index 2870244..848a1d6 100644 --- a/code_comments/templates/comments.html +++ b/code_comments/templates/comments.html @@ -15,6 +15,12 @@

Code Comments

 Filter comments:
+