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
This commit is contained in:
Jean-Philippe Lang 2007-04-24 13:57:27 +00:00
parent ed5b5d0559
commit 941a240535
21 changed files with 266 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -44,7 +44,12 @@ end %>
</tr>
</table>
<hr />
<br />
<% if @issue.changesets.any? %>
<div style="float:right;">
<em><%= l(:label_revision_plural) %>: <%= @issue.changesets.collect{|changeset| link_to(changeset.revision, :controller => 'repositories', :action => 'revision', :id => @project, :rev => changeset.revision)}.join(", ") %></em>
</div>
<% end %>
<b><%=l(:field_description)%> :</b><br /><br />
<%= textilizable @issue.description %>

View File

@ -10,6 +10,15 @@
<p><em><%= @changeset.committer %>, <%= format_time(@changeset.committed_on) %></em></p>
<%= textilizable @changeset.comment %>
<% if @changeset.issues.any? %>
<h3><%= l(:label_related_issues) %></h3>
<ul>
<% @changeset.issues.each do |issue| %>
<li><%= link_to_issue issue %>: <%=h issue.subject %></li>
<% end %>
</ul>
<% end %>
<div style="float:right;">
<div class="square action_A"></div> <div style="float:left;"><%= l(:label_added) %>&nbsp;</div>
<div class="square action_M"></div> <div style="float:left;"><%= l(:label_modified) %>&nbsp;</div>

View File

@ -50,8 +50,18 @@
<p><label><%= l(:setting_sys_api_enabled) %></label>
<%= check_box_tag 'settings[sys_api_enabled]', 1, Setting.sys_api_enabled? %><%= hidden_field_tag 'settings[sys_api_enabled]', 0 %></p>
</div>
<fieldset class="box"><legend><%= l(:text_issues_ref_in_commit_messages) %></legend>
<p><label><%= l(:setting_commit_ref_keywords) %></label>
<%= text_field_tag 'settings[commit_ref_keywords]', Setting.commit_ref_keywords, :size => 30 %><br /><em><%= l(:text_coma_separated) %></em></p>
<p><label><%= l(:setting_commit_fix_keywords) %></label>
<%= text_field_tag 'settings[commit_fix_keywords]', Setting.commit_fix_keywords, :size => 30 %>
&nbsp;<%= 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) %>
<br /><em><%= l(:text_coma_separated) %></em></p>
</fieldset>
<%= submit_tag l(:button_save) %>
</div>
<% end %>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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: 開発者

View File

@ -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

View File

@ -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: 开发人员

View File

@ -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.

38
test/fixtures/changesets.yml vendored Normal file
View File

@ -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

View File

@ -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

8
test/fixtures/repositories.yml vendored Normal file
View File

@ -0,0 +1,8 @@
---
repositories_001:
project_id: 1
url: svn://localhost/test
id: 10
root_url: svn://localhost
password: ""
login: ""

View File

@ -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