From 941a240535ac1b62f210afefefa4a0013b185a5f Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Tue, 24 Apr 2007 13:57:27 +0000 Subject: [PATCH] Commit messages are now scanned for referenced or fixed issue IDs. Keywords and the status to apply to fixed issues can be defined in Admin -> Settings. Default keywords: - for referencing issues: refs, references, IssueID - for fixing issues: fixes,closes There's no default status defined for fixed issue. You'll have to specify it if you want to enable auto closure of issues. Example of a working commit message: "This commit references #1, #2 and fixes #3" git-svn-id: http://redmine.rubyforge.org/svn/trunk@473 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/models/changeset.rb | 38 ++++++++++++++ app/models/issue.rb | 3 +- app/models/repository.rb | 9 ++++ app/views/issues/show.rhtml | 7 ++- app/views/repositories/revision.rhtml | 9 ++++ app/views/settings/edit.rhtml | 12 ++++- config/settings.yml | 8 +++ db/migrate/040_create_changesets_issues.rb | 13 +++++ lang/de.yml | 6 +++ lang/en.yml | 6 +++ lang/es.yml | 6 +++ lang/fr.yml | 6 +++ lang/it.yml | 6 +++ lang/ja.yml | 6 +++ lang/pt.yml | 6 +++ lang/zh.yml | 6 +++ public/stylesheets/application.css | 4 +- test/fixtures/changesets.yml | 38 ++++++++++++++ test/fixtures/issues.yml | 15 ++++++ test/fixtures/repositories.yml | 8 +++ test/unit/repository_test.rb | 59 ++++++++++++++++++++++ 21 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 db/migrate/040_create_changesets_issues.rb create mode 100644 test/fixtures/changesets.yml create mode 100644 test/fixtures/repositories.yml create mode 100644 test/unit/repository_test.rb diff --git a/app/models/changeset.rb b/app/models/changeset.rb index 00775f337..824fa12f5 100644 --- a/app/models/changeset.rb +++ b/app/models/changeset.rb @@ -18,6 +18,7 @@ class Changeset < ActiveRecord::Base belongs_to :repository has_many :changes, :dependent => :delete_all + has_and_belongs_to_many :issues validates_presence_of :repository_id, :revision, :committed_on, :commit_date validates_numericality_of :revision, :only_integer => true @@ -27,4 +28,41 @@ class Changeset < ActiveRecord::Base self.commit_date = date super end + + def after_create + scan_comment_for_issue_ids + end + + def scan_comment_for_issue_ids + return if comment.blank? + # keywords used to reference issues + ref_keywords = Setting.commit_ref_keywords.downcase.split(",") + # keywords used to fix issues + fix_keywords = Setting.commit_fix_keywords.downcase.split(",") + # status applied + fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id) + + kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw.strip)}.join("|") + return if kw_regexp.blank? + + # remove any associated issues + self.issues.clear + + comment.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match| + action = match[0] + target_issue_ids = match[1].scan(/\d+/) + target_issues = repository.project.issues.find_all_by_id(target_issue_ids) + if fix_status && fix_keywords.include?(action.downcase) + # update status of issues + logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug? + target_issues.each do |issue| + # don't change the status is the issue is already closed + next if issue.status.is_closed? + issue.status = fix_status + issue.save + end + end + self.issues << target_issues + end + end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 0f44cdd30..72a953f62 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -31,7 +31,8 @@ class Issue < ActiveRecord::Base has_many :time_entries has_many :custom_values, :dependent => :delete_all, :as => :customized has_many :custom_fields, :through => :custom_values - + has_and_belongs_to_many :changesets, :order => "revision ASC" + acts_as_watchable validates_presence_of :subject, :description, :priority, :tracker, :author, :status diff --git a/app/models/repository.rb b/app/models/repository.rb index 8b8de5c42..5b7feb79f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -79,10 +79,19 @@ class Repository < ActiveRecord::Base end end + def scan_changesets_for_issue_ids + self.changesets.each(&:scan_comment_for_issue_ids) + end + # fetch new changesets for all repositories # can be called periodically by an external script # eg. ruby script/runner "Repository.fetch_changesets" def self.fetch_changesets find(:all).each(&:fetch_changesets) end + + # scan changeset comments to find related and fixed issues for all repositories + def self.scan_changesets_for_issue_ids + find(:all).each(&:scan_changesets_for_issue_ids) + end end diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml index a967b57db..2eb01f094 100644 --- a/app/views/issues/show.rhtml +++ b/app/views/issues/show.rhtml @@ -44,7 +44,12 @@ end %>
-
+ +<% if @issue.changesets.any? %> +
+ <%= l(:label_revision_plural) %>: <%= @issue.changesets.collect{|changeset| link_to(changeset.revision, :controller => 'repositories', :action => 'revision', :id => @project, :rev => changeset.revision)}.join(", ") %> +
+<% end %> <%=l(:field_description)%> :

<%= textilizable @issue.description %> diff --git a/app/views/repositories/revision.rhtml b/app/views/repositories/revision.rhtml index f738f8cda..b443e8c64 100644 --- a/app/views/repositories/revision.rhtml +++ b/app/views/repositories/revision.rhtml @@ -10,6 +10,15 @@

<%= @changeset.committer %>, <%= format_time(@changeset.committed_on) %>

<%= textilizable @changeset.comment %> +<% if @changeset.issues.any? %> +

<%= l(:label_related_issues) %>

+ +<% end %> +
<%= l(:label_added) %> 
<%= l(:label_modified) %> 
diff --git a/app/views/settings/edit.rhtml b/app/views/settings/edit.rhtml index 7a678c53f..4c9b55029 100644 --- a/app/views/settings/edit.rhtml +++ b/app/views/settings/edit.rhtml @@ -50,8 +50,18 @@

<%= check_box_tag 'settings[sys_api_enabled]', 1, Setting.sys_api_enabled? %><%= hidden_field_tag 'settings[sys_api_enabled]', 0 %>

-
+ +
<%= l(:text_issues_ref_in_commit_messages) %> +

+<%= text_field_tag 'settings[commit_ref_keywords]', Setting.commit_ref_keywords, :size => 30 %>
<%= l(:text_coma_separated) %>

+ +

+<%= text_field_tag 'settings[commit_fix_keywords]', Setting.commit_fix_keywords, :size => 30 %> + <%= l(:label_applied_status) %>: <%= select_tag 'settings[commit_fix_status_id]', options_for_select( [["", 0]] + IssueStatus.find(:all).collect{|status| [status.name, status.id.to_s]}, Setting.commit_fix_status_id) %> +
<%= l(:text_coma_separated) %>

+
+ <%= submit_tag l(:button_save) %> <% end %> \ No newline at end of file diff --git a/config/settings.yml b/config/settings.yml index 979e9e7f7..46324785b 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -54,3 +54,11 @@ autofetch_changesets: default: 1 sys_api_enabled: default: 0 +commit_ref_keywords: + default: 'refs,references,IssueID' +commit_fix_keywords: + default: 'fixes,closes' +commit_fix_status_id: + format: int + default: 0 + \ No newline at end of file diff --git a/db/migrate/040_create_changesets_issues.rb b/db/migrate/040_create_changesets_issues.rb new file mode 100644 index 000000000..494d3cc46 --- /dev/null +++ b/db/migrate/040_create_changesets_issues.rb @@ -0,0 +1,13 @@ +class CreateChangesetsIssues < ActiveRecord::Migration + def self.up + create_table :changesets_issues, :id => false do |t| + t.column :changeset_id, :integer, :null => false + t.column :issue_id, :integer, :null => false + end + add_index :changesets_issues, [:changeset_id, :issue_id], :unique => true, :name => :changesets_issues_ids + end + + def self.down + drop_table :changesets_issues + end +end diff --git a/lang/de.yml b/lang/de.yml index f8b7bc0da..40122081b 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -164,6 +164,8 @@ setting_wiki_compression: Wiki-Historie komprimieren setting_feeds_limit: Limit Feed Inhalt setting_autofetch_changesets: Autofetch SVN commits setting_sys_api_enabled: Enable WS for repository management +setting_commit_ref_keywords: Referencing keywords +setting_commit_fix_keywords: Fixing keywords label_user: Benutzer label_user_plural: Benutzer @@ -358,6 +360,8 @@ label_options: Options label_copy_workflow_from: Copy workflow from label_permissions_report: Permissions report label_watched_issues: Watched issues +label_related_issues: Related issues +label_applied_status: Applied status button_login: Einloggen button_submit: OK @@ -408,6 +412,8 @@ text_caracters_maximum: %d characters maximum. text_length_between: Length between %d and %d characters. text_tracker_no_workflow: No workflow defined for this tracker text_unallowed_characters: Unallowed characters +text_coma_separated: Multiple values allowed (coma separated). +text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages default_role_manager: Manager default_role_developper: Developer diff --git a/lang/en.yml b/lang/en.yml index d26bb8a75..2c1ea280c 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -164,6 +164,8 @@ setting_wiki_compression: Wiki history compression setting_feeds_limit: Feed content limit setting_autofetch_changesets: Autofetch SVN commits setting_sys_api_enabled: Enable WS for repository management +setting_commit_ref_keywords: Referencing keywords +setting_commit_fix_keywords: Fixing keywords label_user: User label_user_plural: Users @@ -358,6 +360,8 @@ label_options: Options label_copy_workflow_from: Copy workflow from label_permissions_report: Permissions report label_watched_issues: Watched issues +label_related_issues: Related issues +label_applied_status: Applied status button_login: Login button_submit: Submit @@ -408,6 +412,8 @@ text_caracters_maximum: %d characters maximum. text_length_between: Length between %d and %d characters. text_tracker_no_workflow: No workflow defined for this tracker text_unallowed_characters: Unallowed characters +text_coma_separated: Multiple values allowed (coma separated). +text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages default_role_manager: Manager default_role_developper: Developer diff --git a/lang/es.yml b/lang/es.yml index 0e31980f2..12f52301a 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -164,6 +164,8 @@ setting_wiki_compression: Compresión de la historia de Wiki setting_feeds_limit: Feed content limit setting_autofetch_changesets: Autofetch SVN commits setting_sys_api_enabled: Enable WS for repository management +setting_commit_ref_keywords: Referencing keywords +setting_commit_fix_keywords: Fixing keywords label_user: Usuario label_user_plural: Usuarios @@ -358,6 +360,8 @@ label_options: Options label_copy_workflow_from: Copy workflow from label_permissions_report: Permissions report label_watched_issues: Watched issues +label_related_issues: Related issues +label_applied_status: Applied status button_login: Conexión button_submit: Someter @@ -408,6 +412,8 @@ text_caracters_maximum: %d characters maximum. text_length_between: Length between %d and %d characters. text_tracker_no_workflow: No workflow defined for this tracker text_unallowed_characters: Unallowed characters +text_coma_separated: Multiple values allowed (coma separated). +text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages default_role_manager: Manager default_role_developper: Desarrollador diff --git a/lang/fr.yml b/lang/fr.yml index 42b46eeba..68336f6f4 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -164,6 +164,8 @@ setting_wiki_compression: Compression historique wiki setting_feeds_limit: Limite du contenu des flux RSS setting_autofetch_changesets: Récupération auto. des commits SVN setting_sys_api_enabled: Activer les WS pour la gestion des dépôts +setting_commit_ref_keywords: Mot-clés de référencement +setting_commit_fix_keywords: Mot-clés de résolution label_user: Utilisateur label_user_plural: Utilisateurs @@ -358,6 +360,8 @@ label_options: Options label_copy_workflow_from: Copier le workflow de label_permissions_report: Synthèse des permissions label_watched_issues: Demandes surveillées +label_related_issues: Demandes liées +label_applied_status: Statut appliqué button_login: Connexion button_submit: Soumettre @@ -408,6 +412,8 @@ text_caracters_maximum: %d caractères maximum. text_length_between: Longueur comprise entre %d et %d caractères. text_tracker_no_workflow: Aucun worflow n'est défini pour ce tracker text_unallowed_characters: Caractères non autorisés +text_coma_separated: Plusieurs valeurs possibles (séparées par des virgules). +text_issues_ref_in_commit_messages: Référencement et résolution des demandes dans les commentaires SVN default_role_manager: Manager default_role_developper: Développeur diff --git a/lang/it.yml b/lang/it.yml index 38ad4d6d6..0c12db675 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -164,6 +164,8 @@ setting_wiki_compression: Compressione di storia di Wiki setting_feeds_limit: Limite contenuti del feed setting_autofetch_changesets: Acquisisci automaticamente le commit SVN setting_sys_api_enabled: Abilita WS per la gestione del repository +setting_commit_ref_keywords: Referencing keywords +setting_commit_fix_keywords: Fixing keywords label_user: Utente label_user_plural: Utenti @@ -358,6 +360,8 @@ label_options: Opzioni label_copy_workflow_from: Copia workflow da label_permissions_report: Report permessi label_watched_issues: Watched issues +label_related_issues: Related issues +label_applied_status: Applied status button_login: Login button_submit: Invia @@ -408,6 +412,8 @@ text_caracters_maximum: massimo %d caratteri. text_length_between: Lunghezza compresa tra %d e %d caratteri. text_tracker_no_workflow: Nessun workflow definito per questo tracker text_unallowed_characters: Unallowed characters +text_coma_separated: Multiple values allowed (coma separated). +text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages default_role_manager: Manager default_role_developper: Sviluppatore diff --git a/lang/ja.yml b/lang/ja.yml index 66accb7f9..82f430ead 100644 --- a/lang/ja.yml +++ b/lang/ja.yml @@ -165,6 +165,8 @@ setting_wiki_compression: Wiki履歴を圧縮する setting_feeds_limit: フィード内容の上限 setting_autofetch_changesets: SVNコミットを自動取得する setting_sys_api_enabled: リポジトリ管理用のWeb Serviceを有効化する +setting_commit_ref_keywords: Referencing keywords +setting_commit_fix_keywords: Fixing keywords label_user: ユーザ label_user_plural: ユーザ @@ -359,6 +361,8 @@ label_options: オプション label_copy_workflow_from: ワークフローをここからコピー label_permissions_report: 権限レポート label_watched_issues: Watched issues +label_related_issues: Related issues +label_applied_status: Applied status button_login: ログイン button_submit: 変更 @@ -409,6 +413,8 @@ text_caracters_maximum: 最大 %d 文字です。 text_length_between: 長さは %d から %d 文字までです。 text_tracker_no_workflow: このトラッカーにワークフローが定義されていません text_unallowed_characters: Unallowed characters +text_coma_separated: Multiple values allowed (coma separated). +text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages default_role_manager: 管理者 default_role_developper: 開発者 diff --git a/lang/pt.yml b/lang/pt.yml index 0bb4156a5..8d3bb2146 100644 --- a/lang/pt.yml +++ b/lang/pt.yml @@ -164,6 +164,8 @@ setting_wiki_compression: Compactacao do historio do Wiki setting_feeds_limit: Limite do Feed setting_autofetch_changesets: Autofetch SVN commits setting_sys_api_enabled: Ativa WS para gerenciamento do repositorio +setting_commit_ref_keywords: Referencing keywords +setting_commit_fix_keywords: Fixing keywords label_user: Usuario label_user_plural: Usuarios @@ -358,6 +360,8 @@ label_options: Opcoes label_copy_workflow_from: Copiar workflow de label_permissions_report: Relatorio de permissoes label_watched_issues: Watched issues +label_related_issues: Related issues +label_applied_status: Applied status button_login: Login button_submit: Enviar @@ -408,6 +412,8 @@ text_caracters_maximum: %d maximo de caracteres text_length_between: Tamanho entre %d e %d caracteres. text_tracker_no_workflow: Sem workflow definido para este tipo. text_unallowed_characters: Unallowed characters +text_coma_separated: Multiple values allowed (coma separated). +text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages default_role_manager: Analista de Negocio ou Gerente de Projeto default_role_developper: Desenvolvedor diff --git a/lang/zh.yml b/lang/zh.yml index 493838dc3..1b6556a86 100644 --- a/lang/zh.yml +++ b/lang/zh.yml @@ -167,6 +167,8 @@ setting_wiki_compression: Wiki history compression setting_feeds_limit: Feed content limit setting_autofetch_changesets: Autofetch SVN commits setting_sys_api_enabled: Enable WS for repository management +setting_commit_ref_keywords: Referencing keywords +setting_commit_fix_keywords: Fixing keywords label_user: 用户 label_user_plural: 用户列表 @@ -361,6 +363,8 @@ label_options: Options label_copy_workflow_from: Copy workflow from label_permissions_report: Permissions report label_watched_issues: Watched issues +label_related_issues: Related issues +label_applied_status: Applied status button_login: 登录 button_submit: 提交 @@ -411,6 +415,8 @@ text_caracters_maximum: %d characters maximum. text_length_between: Length between %d and %d characters. text_tracker_no_workflow: No workflow defined for this tracker text_unallowed_characters: Unallowed characters +text_coma_separated: Multiple values allowed (coma separated). +text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages default_role_manager: 管理员 default_role_developper: 开发人员 diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 4e845f2c8..3ca6e40e0 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -602,8 +602,8 @@ margin*/ color: #cc0000; } -#settings .tabular p{ padding-left: 250px; } -#settings .tabular label{ margin-left: -250px; width: 245px; } +#settings .tabular p{ padding-left: 300px; } +#settings .tabular label{ margin-left: -300px; width: 295px; } /*.threepxfix class below: Targets IE6- ONLY. Adds 3 pixel indent for multi-line form contents. diff --git a/test/fixtures/changesets.yml b/test/fixtures/changesets.yml new file mode 100644 index 000000000..10adc5d8f --- /dev/null +++ b/test/fixtures/changesets.yml @@ -0,0 +1,38 @@ +--- +changesets_001: + commit_date: 2007-04-11 + committed_on: 2007-04-11 15:14:44 +02:00 + revision: 1 + id: 100 + comment: My very first commit + repository_id: 10 + committer: dlopper +changesets_002: + commit_date: 2007-04-12 + committed_on: 2007-04-12 15:14:44 +02:00 + revision: 2 + id: 101 + comment: 'This commit fixes #1, #2 and references #3' + repository_id: 10 + committer: dlopper +changesets_003: + commit_date: 2007-04-12 + committed_on: 2007-04-12 15:14:44 +02:00 + revision: 3 + id: 102 + comment: |- + A commit with wrong issue ids + IssueID 666 3 + repository_id: 10 + committer: dlopper +changesets_004: + commit_date: 2007-04-12 + committed_on: 2007-04-12 15:14:44 +02:00 + revision: 4 + id: 103 + comment: |- + A commit with an issue id of an other project + IssueID 4 2 + repository_id: 10 + committer: dlopper + \ No newline at end of file diff --git a/test/fixtures/issues.yml b/test/fixtures/issues.yml index 5719a9bc9..ef4ef9de8 100644 --- a/test/fixtures/issues.yml +++ b/test/fixtures/issues.yml @@ -41,3 +41,18 @@ issues_003: assigned_to_id: author_id: 2 status_id: 1 +issues_004: + created_on: 2006-07-19 21:07:27 +02:00 + project_id: 2 + updated_on: 2006-07-19 21:07:27 +02:00 + priority_id: 4 + subject: Issue on project 2 + id: 4 + fixed_version_id: + category_id: + description: Issue on project 2 + tracker_id: 1 + assigned_to_id: + author_id: 2 + status_id: 1 + \ No newline at end of file diff --git a/test/fixtures/repositories.yml b/test/fixtures/repositories.yml new file mode 100644 index 000000000..6d288b192 --- /dev/null +++ b/test/fixtures/repositories.yml @@ -0,0 +1,8 @@ +--- +repositories_001: + project_id: 1 + url: svn://localhost/test + id: 10 + root_url: svn://localhost + password: "" + login: "" diff --git a/test/unit/repository_test.rb b/test/unit/repository_test.rb new file mode 100644 index 000000000..5e0fb56fd --- /dev/null +++ b/test/unit/repository_test.rb @@ -0,0 +1,59 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.dirname(__FILE__) + '/../test_helper' + +class RepositoryTest < Test::Unit::TestCase + fixtures :projects, :repositories, :issues, :issue_statuses, :changesets + + def test_create + repository = Repository.new(:project => Project.find(2)) + assert !repository.save + + repository.url = "svn://localhost" + assert repository.save + repository.reload + + project = Project.find(2) + assert_equal repository, project.repository + end + + def test_cant_change_url + repository = Project.find(1).repository + url = repository.url + repository.url = "svn://anotherhost" + assert_equal url, repository.url + end + + def test_scan_changesets_for_issue_ids + # choosing a status to apply to fix issues + Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id + + # make sure issue 1 is not already closed + assert !Issue.find(1).status.is_closed? + + Repository.scan_changesets_for_issue_ids + assert_equal [101, 102], Issue.find(3).changeset_ids + + # fixed issues + assert Issue.find(1).status.is_closed? + assert_equal [101], Issue.find(1).changeset_ids + + # ignoring commits referencing an issue of another project + assert_equal [], Issue.find(4).changesets + end +end