##// END OF EJS Templates
Eager-load users....
Jean-Philippe Lang -
r2007:e8d0c26e4393
parent child
Show More
@@ -1,179 +1,179
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Repository < ActiveRecord::Base
18 class Repository < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
20 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
21 has_many :changes, :through => :changesets
21 has_many :changes, :through => :changesets
22
22
23 # Raw SQL to delete changesets and changes in the database
23 # Raw SQL to delete changesets and changes in the database
24 # has_many :changesets, :dependent => :destroy is too slow for big repositories
24 # has_many :changesets, :dependent => :destroy is too slow for big repositories
25 before_destroy :clear_changesets
25 before_destroy :clear_changesets
26
26
27 # Checks if the SCM is enabled when creating a repository
27 # Checks if the SCM is enabled when creating a repository
28 validate_on_create { |r| r.errors.add(:type, :activerecord_error_invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
28 validate_on_create { |r| r.errors.add(:type, :activerecord_error_invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
29
29
30 # Removes leading and trailing whitespace
30 # Removes leading and trailing whitespace
31 def url=(arg)
31 def url=(arg)
32 write_attribute(:url, arg ? arg.to_s.strip : nil)
32 write_attribute(:url, arg ? arg.to_s.strip : nil)
33 end
33 end
34
34
35 # Removes leading and trailing whitespace
35 # Removes leading and trailing whitespace
36 def root_url=(arg)
36 def root_url=(arg)
37 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
37 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
38 end
38 end
39
39
40 def scm
40 def scm
41 @scm ||= self.scm_adapter.new url, root_url, login, password
41 @scm ||= self.scm_adapter.new url, root_url, login, password
42 update_attribute(:root_url, @scm.root_url) if root_url.blank?
42 update_attribute(:root_url, @scm.root_url) if root_url.blank?
43 @scm
43 @scm
44 end
44 end
45
45
46 def scm_name
46 def scm_name
47 self.class.scm_name
47 self.class.scm_name
48 end
48 end
49
49
50 def supports_cat?
50 def supports_cat?
51 scm.supports_cat?
51 scm.supports_cat?
52 end
52 end
53
53
54 def supports_annotate?
54 def supports_annotate?
55 scm.supports_annotate?
55 scm.supports_annotate?
56 end
56 end
57
57
58 def entry(path=nil, identifier=nil)
58 def entry(path=nil, identifier=nil)
59 scm.entry(path, identifier)
59 scm.entry(path, identifier)
60 end
60 end
61
61
62 def entries(path=nil, identifier=nil)
62 def entries(path=nil, identifier=nil)
63 scm.entries(path, identifier)
63 scm.entries(path, identifier)
64 end
64 end
65
65
66 def properties(path, identifier=nil)
66 def properties(path, identifier=nil)
67 scm.properties(path, identifier)
67 scm.properties(path, identifier)
68 end
68 end
69
69
70 def cat(path, identifier=nil)
70 def cat(path, identifier=nil)
71 scm.cat(path, identifier)
71 scm.cat(path, identifier)
72 end
72 end
73
73
74 def diff(path, rev, rev_to)
74 def diff(path, rev, rev_to)
75 scm.diff(path, rev, rev_to)
75 scm.diff(path, rev, rev_to)
76 end
76 end
77
77
78 # Default behaviour: we search in cached changesets
78 # Default behaviour: we search in cached changesets
79 def changesets_for_path(path)
79 def changesets_for_path(path)
80 path = "/#{path}" unless path.starts_with?('/')
80 path = "/#{path}" unless path.starts_with?('/')
81 Change.find(:all, :include => :changeset,
81 Change.find(:all, :include => {:changeset => :user},
82 :conditions => ["repository_id = ? AND path = ?", id, path],
82 :conditions => ["repository_id = ? AND path = ?", id, path],
83 :order => "committed_on DESC, #{Changeset.table_name}.id DESC").collect(&:changeset)
83 :order => "committed_on DESC, #{Changeset.table_name}.id DESC").collect(&:changeset)
84 end
84 end
85
85
86 # Returns a path relative to the url of the repository
86 # Returns a path relative to the url of the repository
87 def relative_path(path)
87 def relative_path(path)
88 path
88 path
89 end
89 end
90
90
91 def latest_changeset
91 def latest_changeset
92 @latest_changeset ||= changesets.find(:first)
92 @latest_changeset ||= changesets.find(:first)
93 end
93 end
94
94
95 def scan_changesets_for_issue_ids
95 def scan_changesets_for_issue_ids
96 self.changesets.each(&:scan_comment_for_issue_ids)
96 self.changesets.each(&:scan_comment_for_issue_ids)
97 end
97 end
98
98
99 # Returns an array of committers usernames and associated user_id
99 # Returns an array of committers usernames and associated user_id
100 def committers
100 def committers
101 @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
101 @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
102 end
102 end
103
103
104 # Maps committers username to a user ids
104 # Maps committers username to a user ids
105 def committer_ids=(h)
105 def committer_ids=(h)
106 if h.is_a?(Hash)
106 if h.is_a?(Hash)
107 committers.each do |committer, user_id|
107 committers.each do |committer, user_id|
108 new_user_id = h[committer]
108 new_user_id = h[committer]
109 if new_user_id && (new_user_id.to_i != user_id.to_i)
109 if new_user_id && (new_user_id.to_i != user_id.to_i)
110 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
110 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
111 Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
111 Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
112 end
112 end
113 end
113 end
114 @committers = nil
114 @committers = nil
115 true
115 true
116 else
116 else
117 false
117 false
118 end
118 end
119 end
119 end
120
120
121 # Returns the Redmine User corresponding to the given +committer+
121 # Returns the Redmine User corresponding to the given +committer+
122 # It will return nil if the committer is not yet mapped and if no User
122 # It will return nil if the committer is not yet mapped and if no User
123 # with the same username or email was found
123 # with the same username or email was found
124 def find_committer_user(committer)
124 def find_committer_user(committer)
125 if committer
125 if committer
126 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
126 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
127 if c && c.user
127 if c && c.user
128 c.user
128 c.user
129 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
129 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
130 username, email = $1.strip, $3
130 username, email = $1.strip, $3
131 u = User.find_by_login(username)
131 u = User.find_by_login(username)
132 u ||= User.find_by_mail(email) unless email.blank?
132 u ||= User.find_by_mail(email) unless email.blank?
133 u
133 u
134 end
134 end
135 end
135 end
136 end
136 end
137
137
138 # fetch new changesets for all repositories
138 # fetch new changesets for all repositories
139 # can be called periodically by an external script
139 # can be called periodically by an external script
140 # eg. ruby script/runner "Repository.fetch_changesets"
140 # eg. ruby script/runner "Repository.fetch_changesets"
141 def self.fetch_changesets
141 def self.fetch_changesets
142 find(:all).each(&:fetch_changesets)
142 find(:all).each(&:fetch_changesets)
143 end
143 end
144
144
145 # scan changeset comments to find related and fixed issues for all repositories
145 # scan changeset comments to find related and fixed issues for all repositories
146 def self.scan_changesets_for_issue_ids
146 def self.scan_changesets_for_issue_ids
147 find(:all).each(&:scan_changesets_for_issue_ids)
147 find(:all).each(&:scan_changesets_for_issue_ids)
148 end
148 end
149
149
150 def self.scm_name
150 def self.scm_name
151 'Abstract'
151 'Abstract'
152 end
152 end
153
153
154 def self.available_scm
154 def self.available_scm
155 subclasses.collect {|klass| [klass.scm_name, klass.name]}
155 subclasses.collect {|klass| [klass.scm_name, klass.name]}
156 end
156 end
157
157
158 def self.factory(klass_name, *args)
158 def self.factory(klass_name, *args)
159 klass = "Repository::#{klass_name}".constantize
159 klass = "Repository::#{klass_name}".constantize
160 klass.new(*args)
160 klass.new(*args)
161 rescue
161 rescue
162 nil
162 nil
163 end
163 end
164
164
165 private
165 private
166
166
167 def before_save
167 def before_save
168 # Strips url and root_url
168 # Strips url and root_url
169 url.strip!
169 url.strip!
170 root_url.strip!
170 root_url.strip!
171 true
171 true
172 end
172 end
173
173
174 def clear_changesets
174 def clear_changesets
175 connection.delete("DELETE FROM changes WHERE changes.changeset_id IN (SELECT changesets.id FROM changesets WHERE changesets.repository_id = #{id})")
175 connection.delete("DELETE FROM changes WHERE changes.changeset_id IN (SELECT changesets.id FROM changesets WHERE changesets.repository_id = #{id})")
176 connection.delete("DELETE FROM changesets_issues WHERE changesets_issues.changeset_id IN (SELECT changesets.id FROM changesets WHERE changesets.repository_id = #{id})")
176 connection.delete("DELETE FROM changesets_issues WHERE changesets_issues.changeset_id IN (SELECT changesets.id FROM changesets WHERE changesets.repository_id = #{id})")
177 connection.delete("DELETE FROM changesets WHERE changesets.repository_id = #{id}")
177 connection.delete("DELETE FROM changesets WHERE changesets.repository_id = #{id}")
178 end
178 end
179 end
179 end
@@ -1,70 +1,70
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 # Copyright (C) 2007 Patrick Aljord patcito@Ε‹mail.com
3 # Copyright (C) 2007 Patrick Aljord patcito@Ε‹mail.com
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redmine/scm/adapters/git_adapter'
18 require 'redmine/scm/adapters/git_adapter'
19
19
20 class Repository::Git < Repository
20 class Repository::Git < Repository
21 attr_protected :root_url
21 attr_protected :root_url
22 validates_presence_of :url
22 validates_presence_of :url
23
23
24 def scm_adapter
24 def scm_adapter
25 Redmine::Scm::Adapters::GitAdapter
25 Redmine::Scm::Adapters::GitAdapter
26 end
26 end
27
27
28 def self.scm_name
28 def self.scm_name
29 'Git'
29 'Git'
30 end
30 end
31
31
32 def changesets_for_path(path)
32 def changesets_for_path(path)
33 Change.find(:all, :include => :changeset,
33 Change.find(:all, :include => {:changeset => :user},
34 :conditions => ["repository_id = ? AND path = ?", id, path],
34 :conditions => ["repository_id = ? AND path = ?", id, path],
35 :order => "committed_on DESC, #{Changeset.table_name}.revision DESC").collect(&:changeset)
35 :order => "committed_on DESC, #{Changeset.table_name}.revision DESC").collect(&:changeset)
36 end
36 end
37
37
38 def fetch_changesets
38 def fetch_changesets
39 scm_info = scm.info
39 scm_info = scm.info
40 if scm_info
40 if scm_info
41 # latest revision found in database
41 # latest revision found in database
42 db_revision = latest_changeset ? latest_changeset.revision : nil
42 db_revision = latest_changeset ? latest_changeset.revision : nil
43 # latest revision in the repository
43 # latest revision in the repository
44 scm_revision = scm_info.lastrev.scmid
44 scm_revision = scm_info.lastrev.scmid
45
45
46 unless changesets.find_by_scmid(scm_revision)
46 unless changesets.find_by_scmid(scm_revision)
47 scm.revisions('', db_revision, nil, :reverse => true) do |revision|
47 scm.revisions('', db_revision, nil, :reverse => true) do |revision|
48 if changesets.find_by_scmid(revision.scmid.to_s).nil?
48 if changesets.find_by_scmid(revision.scmid.to_s).nil?
49 transaction do
49 transaction do
50 changeset = Changeset.create!(:repository => self,
50 changeset = Changeset.create!(:repository => self,
51 :revision => revision.identifier,
51 :revision => revision.identifier,
52 :scmid => revision.scmid,
52 :scmid => revision.scmid,
53 :committer => revision.author,
53 :committer => revision.author,
54 :committed_on => revision.time,
54 :committed_on => revision.time,
55 :comments => revision.message)
55 :comments => revision.message)
56
56
57 revision.paths.each do |change|
57 revision.paths.each do |change|
58 Change.create!(:changeset => changeset,
58 Change.create!(:changeset => changeset,
59 :action => change[:action],
59 :action => change[:action],
60 :path => change[:path],
60 :path => change[:path],
61 :from_path => change[:from_path],
61 :from_path => change[:from_path],
62 :from_revision => change[:from_revision])
62 :from_revision => change[:from_revision])
63 end
63 end
64 end
64 end
65 end
65 end
66 end
66 end
67 end
67 end
68 end
68 end
69 end
69 end
70 end
70 end
@@ -1,89 +1,89
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redmine/scm/adapters/subversion_adapter'
18 require 'redmine/scm/adapters/subversion_adapter'
19
19
20 class Repository::Subversion < Repository
20 class Repository::Subversion < Repository
21 attr_protected :root_url
21 attr_protected :root_url
22 validates_presence_of :url
22 validates_presence_of :url
23 validates_format_of :url, :with => /^(http|https|svn|svn\+ssh|file):\/\/.+/i
23 validates_format_of :url, :with => /^(http|https|svn|svn\+ssh|file):\/\/.+/i
24
24
25 def scm_adapter
25 def scm_adapter
26 Redmine::Scm::Adapters::SubversionAdapter
26 Redmine::Scm::Adapters::SubversionAdapter
27 end
27 end
28
28
29 def self.scm_name
29 def self.scm_name
30 'Subversion'
30 'Subversion'
31 end
31 end
32
32
33 def changesets_for_path(path)
33 def changesets_for_path(path)
34 revisions = scm.revisions(path)
34 revisions = scm.revisions(path)
35 revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC") : []
35 revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : []
36 end
36 end
37
37
38 # Returns a path relative to the url of the repository
38 # Returns a path relative to the url of the repository
39 def relative_path(path)
39 def relative_path(path)
40 path.gsub(Regexp.new("^\/?#{Regexp.escape(relative_url)}"), '')
40 path.gsub(Regexp.new("^\/?#{Regexp.escape(relative_url)}"), '')
41 end
41 end
42
42
43 def fetch_changesets
43 def fetch_changesets
44 scm_info = scm.info
44 scm_info = scm.info
45 if scm_info
45 if scm_info
46 # latest revision found in database
46 # latest revision found in database
47 db_revision = latest_changeset ? latest_changeset.revision.to_i : 0
47 db_revision = latest_changeset ? latest_changeset.revision.to_i : 0
48 # latest revision in the repository
48 # latest revision in the repository
49 scm_revision = scm_info.lastrev.identifier.to_i
49 scm_revision = scm_info.lastrev.identifier.to_i
50 if db_revision < scm_revision
50 if db_revision < scm_revision
51 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
51 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
52 identifier_from = db_revision + 1
52 identifier_from = db_revision + 1
53 while (identifier_from <= scm_revision)
53 while (identifier_from <= scm_revision)
54 # loads changesets by batches of 200
54 # loads changesets by batches of 200
55 identifier_to = [identifier_from + 199, scm_revision].min
55 identifier_to = [identifier_from + 199, scm_revision].min
56 revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
56 revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
57 transaction do
57 transaction do
58 revisions.reverse_each do |revision|
58 revisions.reverse_each do |revision|
59 changeset = Changeset.create(:repository => self,
59 changeset = Changeset.create(:repository => self,
60 :revision => revision.identifier,
60 :revision => revision.identifier,
61 :committer => revision.author,
61 :committer => revision.author,
62 :committed_on => revision.time,
62 :committed_on => revision.time,
63 :comments => revision.message)
63 :comments => revision.message)
64
64
65 revision.paths.each do |change|
65 revision.paths.each do |change|
66 Change.create(:changeset => changeset,
66 Change.create(:changeset => changeset,
67 :action => change[:action],
67 :action => change[:action],
68 :path => change[:path],
68 :path => change[:path],
69 :from_path => change[:from_path],
69 :from_path => change[:from_path],
70 :from_revision => change[:from_revision])
70 :from_revision => change[:from_revision])
71 end
71 end
72 end
72 end
73 end unless revisions.nil?
73 end unless revisions.nil?
74 identifier_from = identifier_to + 1
74 identifier_from = identifier_to + 1
75 end
75 end
76 end
76 end
77 end
77 end
78 end
78 end
79
79
80 private
80 private
81
81
82 # Returns the relative url of the repository
82 # Returns the relative url of the repository
83 # Eg: root_url = file:///var/svn/foo
83 # Eg: root_url = file:///var/svn/foo
84 # url = file:///var/svn/foo/bar
84 # url = file:///var/svn/foo/bar
85 # => returns /bar
85 # => returns /bar
86 def relative_url
86 def relative_url
87 @relative_url ||= url.gsub(Regexp.new("^#{Regexp.escape(root_url)}"), '')
87 @relative_url ||= url.gsub(Regexp.new("^#{Regexp.escape(root_url)}"), '')
88 end
88 end
89 end
89 end
General Comments 0
You need to be logged in to leave comments. Login now