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) %>
+
+<% @changeset.issues.each do |issue| %>
+ - <%= link_to_issue issue %>: <%=h issue.subject %>
+<% end %>
+
+<% 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 %>
-
+
+
+
<%= 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