##// END OF EJS Templates
scm: use i18n string at commit log encoding setting (#1735)....
Toshi MARUYAMA -
r5399:bb929f4f7501
parent child
Show More
@@ -1,262 +1,265
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'iconv'
19 19 require 'redmine/codeset_util'
20 20
21 21 module RepositoriesHelper
22 22 def format_revision(revision)
23 23 if revision.respond_to? :format_identifier
24 24 revision.format_identifier
25 25 else
26 26 revision.to_s
27 27 end
28 28 end
29 29
30 30 def truncate_at_line_break(text, length = 255)
31 31 if text
32 32 text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')
33 33 end
34 34 end
35 35
36 36 def render_properties(properties)
37 37 unless properties.nil? || properties.empty?
38 38 content = ''
39 39 properties.keys.sort.each do |property|
40 40 content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>")
41 41 end
42 42 content_tag('ul', content, :class => 'properties')
43 43 end
44 44 end
45 45
46 46 def render_changeset_changes
47 47 changes = @changeset.changes.find(:all, :limit => 1000, :order => 'path').collect do |change|
48 48 case change.action
49 49 when 'A'
50 50 # Detects moved/copied files
51 51 if !change.from_path.blank?
52 52 change.action =
53 53 @changeset.changes.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
54 54 end
55 55 change
56 56 when 'D'
57 57 @changeset.changes.detect {|c| c.from_path == change.path} ? nil : change
58 58 else
59 59 change
60 60 end
61 61 end.compact
62 62
63 63 tree = { }
64 64 changes.each do |change|
65 65 p = tree
66 66 dirs = change.path.to_s.split('/').select {|d| !d.blank?}
67 67 path = ''
68 68 dirs.each do |dir|
69 69 path += '/' + dir
70 70 p[:s] ||= {}
71 71 p = p[:s]
72 72 p[path] ||= {}
73 73 p = p[path]
74 74 end
75 75 p[:c] = change
76 76 end
77 77 render_changes_tree(tree[:s])
78 78 end
79 79
80 80 def render_changes_tree(tree)
81 81 return '' if tree.nil?
82 82 output = ''
83 83 output << '<ul>'
84 84 tree.keys.sort.each do |file|
85 85 style = 'change'
86 86 text = File.basename(h(file))
87 87 if s = tree[file][:s]
88 88 style << ' folder'
89 89 path_param = to_path_param(@repository.relative_path(file))
90 90 text = link_to(text, :controller => 'repositories',
91 91 :action => 'show',
92 92 :id => @project,
93 93 :path => path_param,
94 94 :rev => @changeset.identifier)
95 95 output << "<li class='#{style}'>#{text}</li>"
96 96 output << render_changes_tree(s)
97 97 elsif c = tree[file][:c]
98 98 style << " change-#{c.action}"
99 99 path_param = to_path_param(@repository.relative_path(c.path))
100 100 text = link_to(text, :controller => 'repositories',
101 101 :action => 'entry',
102 102 :id => @project,
103 103 :path => path_param,
104 104 :rev => @changeset.identifier) unless c.action == 'D'
105 105 text << " - #{c.revision}" unless c.revision.blank?
106 106 text << ' (' + link_to('diff', :controller => 'repositories',
107 107 :action => 'diff',
108 108 :id => @project,
109 109 :path => path_param,
110 110 :rev => @changeset.identifier) + ') ' if c.action == 'M'
111 111 text << ' ' + content_tag('span', c.from_path, :class => 'copied-from') unless c.from_path.blank?
112 112 output << "<li class='#{style}'>#{text}</li>"
113 113 end
114 114 end
115 115 output << '</ul>'
116 116 output
117 117 end
118 118
119 119 def to_utf8(str)
120 120 return str if str.nil?
121 121 str = to_utf8_internal(str)
122 122 if str.respond_to?(:force_encoding)
123 123 str.force_encoding('UTF-8')
124 124 end
125 125 str
126 126 end
127 127
128 128 def to_utf8_internal(str)
129 129 return str if str.nil?
130 130 if str.respond_to?(:force_encoding)
131 131 str.force_encoding('ASCII-8BIT')
132 132 end
133 133 return str if str.empty?
134 134 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
135 135 if str.respond_to?(:force_encoding)
136 136 str.force_encoding('UTF-8')
137 137 end
138 138 @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
139 139 @encodings.each do |encoding|
140 140 begin
141 141 return Iconv.conv('UTF-8', encoding, str)
142 142 rescue Iconv::Failure
143 143 # do nothing here and try the next encoding
144 144 end
145 145 end
146 146 str = Redmine::CodesetUtil.replace_invalid_utf8(str)
147 147 end
148 148 private :to_utf8_internal
149 149
150 150 def repository_field_tags(form, repository)
151 151 method = repository.class.name.demodulize.underscore + "_field_tags"
152 152 if repository.is_a?(Repository) &&
153 153 respond_to?(method) && method != 'repository_field_tags'
154 154 send(method, form, repository)
155 155 end
156 156 end
157 157
158 158 def scm_select_tag(repository)
159 159 scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
160 160 Redmine::Scm::Base.all.each do |scm|
161 161 if Setting.enabled_scm.include?(scm) ||
162 162 (repository && repository.class.name.demodulize == scm)
163 163 scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
164 164 end
165 165 end
166 166 select_tag('repository_scm',
167 167 options_for_select(scm_options, repository.class.name.demodulize),
168 168 :disabled => (repository && !repository.new_record?),
169 169 :onchange => remote_function(
170 170 :url => {
171 171 :controller => 'repositories',
172 172 :action => 'edit',
173 173 :id => @project
174 174 },
175 175 :method => :get,
176 176 :with => "Form.serialize(this.form)")
177 177 )
178 178 end
179 179
180 180 def with_leading_slash(path)
181 181 path.to_s.starts_with?('/') ? path : "/#{path}"
182 182 end
183 183
184 184 def without_leading_slash(path)
185 185 path.gsub(%r{^/+}, '')
186 186 end
187 187
188 188 def subversion_field_tags(form, repository)
189 189 content_tag('p', form.text_field(:url, :size => 60, :required => true,
190 190 :disabled => (repository && !repository.root_url.blank?)) +
191 191 '<br />(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
192 192 content_tag('p', form.text_field(:login, :size => 30)) +
193 193 content_tag('p', form.password_field(
194 194 :password, :size => 30, :name => 'ignore',
195 195 :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
196 196 :onfocus => "this.value=''; this.name='repository[password]';",
197 197 :onchange => "this.name='repository[password]';"))
198 198 end
199 199
200 200 def darcs_field_tags(form, repository)
201 201 content_tag('p', form.text_field(:url, :label => 'Root directory',
202 202 :size => 60, :required => true,
203 203 :disabled => (repository && !repository.new_record?))) +
204 content_tag('p', form.select(:log_encoding, [nil] + Setting::ENCODINGS,
205 :label => 'Commit messages encoding', :required => true))
204 content_tag('p', form.select(
205 :log_encoding, [nil] + Setting::ENCODINGS,
206 :label => l("field_commit_logs_encoding"), :required => true))
206 207 end
207 208
208 209 def mercurial_field_tags(form, repository)
209 210 content_tag('p', form.text_field(:url, :label => 'Root directory',
210 211 :size => 60, :required => true,
211 212 :disabled => (repository && !repository.root_url.blank?)) +
212 213 '<br />Local repository (e.g. /hgrepo, c:\hgrepo)' ) +
213 214 content_tag('p', form.select(
214 215 :path_encoding, [nil] + Setting::ENCODINGS,
215 216 :label => 'Path encoding') +
216 217 '<br />Default: UTF-8')
217 218 end
218 219
219 220 def git_field_tags(form, repository)
220 221 content_tag('p', form.text_field(:url, :label => 'Path to repository',
221 222 :size => 60, :required => true,
222 223 :disabled => (repository && !repository.root_url.blank?)) +
223 224 '<br />Bare and local repository (e.g. /gitrepo, c:\gitrepo)') +
224 225 content_tag('p', form.select(
225 226 :path_encoding, [nil] + Setting::ENCODINGS,
226 227 :label => 'Path encoding') +
227 228 '<br />Default: UTF-8')
228 229 end
229 230
230 231 def cvs_field_tags(form, repository)
231 232 content_tag('p', form.text_field(:root_url,
232 233 :label => 'CVSROOT', :size => 60, :required => true,
233 234 :disabled => !repository.new_record?)) +
234 235 content_tag('p', form.text_field(:url, :label => 'Module',
235 236 :size => 30, :required => true,
236 237 :disabled => !repository.new_record?)) +
237 content_tag('p', form.select(:log_encoding, [nil] + Setting::ENCODINGS,
238 :label => 'Commit messages encoding', :required => true)) +
238 content_tag('p', form.select(
239 :log_encoding, [nil] + Setting::ENCODINGS,
240 :label => l("field_commit_logs_encoding"), :required => true)) +
239 241 content_tag('p', form.select(
240 242 :path_encoding, [nil] + Setting::ENCODINGS,
241 243 :label => 'Path encoding') +
242 244 '<br />Default: UTF-8')
243 245 end
244 246
245 247 def bazaar_field_tags(form, repository)
246 248 content_tag('p', form.text_field(:url, :label => 'Root directory',
247 249 :size => 60, :required => true,
248 250 :disabled => (repository && !repository.new_record?))) +
249 content_tag('p', form.select(:log_encoding, [nil] + Setting::ENCODINGS,
250 :label => 'Commit messages encoding', :required => true))
251 content_tag('p', form.select(
252 :log_encoding, [nil] + Setting::ENCODINGS,
253 :label => l("field_commit_logs_encoding"), :required => true))
251 254 end
252 255
253 256 def filesystem_field_tags(form, repository)
254 257 content_tag('p', form.text_field(:url, :label => 'Root directory',
255 258 :size => 60, :required => true,
256 259 :disabled => (repository && !repository.root_url.blank?))) +
257 260 content_tag('p', form.select(
258 261 :path_encoding, [nil] + Setting::ENCODINGS,
259 262 :label => 'Path encoding') +
260 263 '<br />Default: UTF-8')
261 264 end
262 265 end
@@ -1,290 +1,298
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Repository < ActiveRecord::Base
19 19 include Redmine::Ciphering
20 20
21 21 belongs_to :project
22 22 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
23 23 has_many :changes, :through => :changesets
24 24
25 25 # Raw SQL to delete changesets and changes in the database
26 26 # has_many :changesets, :dependent => :destroy is too slow for big repositories
27 27 before_destroy :clear_changesets
28 28
29 29 validates_length_of :password, :maximum => 255, :allow_nil => true
30 30 # Checks if the SCM is enabled when creating a repository
31 31 validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
32 32
33 def self.human_attribute_name(attribute_key_name)
34 attr_name = attribute_key_name
35 if attr_name == "log_encoding"
36 attr_name = "commit_logs_encoding"
37 end
38 super(attr_name)
39 end
40
33 41 # Removes leading and trailing whitespace
34 42 def url=(arg)
35 43 write_attribute(:url, arg ? arg.to_s.strip : nil)
36 44 end
37 45
38 46 # Removes leading and trailing whitespace
39 47 def root_url=(arg)
40 48 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
41 49 end
42 50
43 51 def password
44 52 read_ciphered_attribute(:password)
45 53 end
46 54
47 55 def password=(arg)
48 56 write_ciphered_attribute(:password, arg)
49 57 end
50 58
51 59 def scm_adapter
52 60 self.class.scm_adapter_class
53 61 end
54 62
55 63 def scm
56 64 @scm ||= self.scm_adapter.new(url, root_url,
57 65 login, password, path_encoding)
58 66 update_attribute(:root_url, @scm.root_url) if root_url.blank?
59 67 @scm
60 68 end
61 69
62 70 def scm_name
63 71 self.class.scm_name
64 72 end
65 73
66 74 def supports_cat?
67 75 scm.supports_cat?
68 76 end
69 77
70 78 def supports_annotate?
71 79 scm.supports_annotate?
72 80 end
73 81
74 82 def supports_all_revisions?
75 83 true
76 84 end
77 85
78 86 def supports_directory_revisions?
79 87 false
80 88 end
81 89
82 90 def entry(path=nil, identifier=nil)
83 91 scm.entry(path, identifier)
84 92 end
85 93
86 94 def entries(path=nil, identifier=nil)
87 95 scm.entries(path, identifier)
88 96 end
89 97
90 98 def branches
91 99 scm.branches
92 100 end
93 101
94 102 def tags
95 103 scm.tags
96 104 end
97 105
98 106 def default_branch
99 107 scm.default_branch
100 108 end
101 109
102 110 def properties(path, identifier=nil)
103 111 scm.properties(path, identifier)
104 112 end
105 113
106 114 def cat(path, identifier=nil)
107 115 scm.cat(path, identifier)
108 116 end
109 117
110 118 def diff(path, rev, rev_to)
111 119 scm.diff(path, rev, rev_to)
112 120 end
113 121
114 122 def diff_format_revisions(cs, cs_to, sep=':')
115 123 text = ""
116 124 text << cs_to.format_identifier + sep if cs_to
117 125 text << cs.format_identifier if cs
118 126 text
119 127 end
120 128
121 129 # Returns a path relative to the url of the repository
122 130 def relative_path(path)
123 131 path
124 132 end
125 133
126 134 # Finds and returns a revision with a number or the beginning of a hash
127 135 def find_changeset_by_name(name)
128 136 return nil if name.blank?
129 137 changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
130 138 end
131 139
132 140 def latest_changeset
133 141 @latest_changeset ||= changesets.find(:first)
134 142 end
135 143
136 144 # Returns the latest changesets for +path+
137 145 # Default behaviour is to search in cached changesets
138 146 def latest_changesets(path, rev, limit=10)
139 147 if path.blank?
140 148 changesets.find(:all, :include => :user,
141 149 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
142 150 :limit => limit)
143 151 else
144 152 changes.find(:all, :include => {:changeset => :user},
145 153 :conditions => ["path = ?", path.with_leading_slash],
146 154 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
147 155 :limit => limit).collect(&:changeset)
148 156 end
149 157 end
150 158
151 159 def scan_changesets_for_issue_ids
152 160 self.changesets.each(&:scan_comment_for_issue_ids)
153 161 end
154 162
155 163 # Returns an array of committers usernames and associated user_id
156 164 def committers
157 165 @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
158 166 end
159 167
160 168 # Maps committers username to a user ids
161 169 def committer_ids=(h)
162 170 if h.is_a?(Hash)
163 171 committers.each do |committer, user_id|
164 172 new_user_id = h[committer]
165 173 if new_user_id && (new_user_id.to_i != user_id.to_i)
166 174 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
167 175 Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
168 176 end
169 177 end
170 178 @committers = nil
171 179 @found_committer_users = nil
172 180 true
173 181 else
174 182 false
175 183 end
176 184 end
177 185
178 186 # Returns the Redmine User corresponding to the given +committer+
179 187 # It will return nil if the committer is not yet mapped and if no User
180 188 # with the same username or email was found
181 189 def find_committer_user(committer)
182 190 unless committer.blank?
183 191 @found_committer_users ||= {}
184 192 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
185 193
186 194 user = nil
187 195 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
188 196 if c && c.user
189 197 user = c.user
190 198 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
191 199 username, email = $1.strip, $3
192 200 u = User.find_by_login(username)
193 201 u ||= User.find_by_mail(email) unless email.blank?
194 202 user = u
195 203 end
196 204 @found_committer_users[committer] = user
197 205 user
198 206 end
199 207 end
200 208
201 209 def repo_log_encoding
202 210 encoding = log_encoding.to_s.strip
203 211 encoding.blank? ? 'UTF-8' : encoding
204 212 end
205 213
206 214 # Fetches new changesets for all repositories of active projects
207 215 # Can be called periodically by an external script
208 216 # eg. ruby script/runner "Repository.fetch_changesets"
209 217 def self.fetch_changesets
210 218 Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
211 219 if project.repository
212 220 begin
213 221 project.repository.fetch_changesets
214 222 rescue Redmine::Scm::Adapters::CommandFailed => e
215 223 logger.error "scm: error during fetching changesets: #{e.message}"
216 224 end
217 225 end
218 226 end
219 227 end
220 228
221 229 # scan changeset comments to find related and fixed issues for all repositories
222 230 def self.scan_changesets_for_issue_ids
223 231 find(:all).each(&:scan_changesets_for_issue_ids)
224 232 end
225 233
226 234 def self.scm_name
227 235 'Abstract'
228 236 end
229 237
230 238 def self.available_scm
231 239 subclasses.collect {|klass| [klass.scm_name, klass.name]}
232 240 end
233 241
234 242 def self.factory(klass_name, *args)
235 243 klass = "Repository::#{klass_name}".constantize
236 244 klass.new(*args)
237 245 rescue
238 246 nil
239 247 end
240 248
241 249 def self.scm_adapter_class
242 250 nil
243 251 end
244 252
245 253 def self.scm_command
246 254 ret = ""
247 255 begin
248 256 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
249 257 rescue Redmine::Scm::Adapters::CommandFailed => e
250 258 logger.error "scm: error during get command: #{e.message}"
251 259 end
252 260 ret
253 261 end
254 262
255 263 def self.scm_version_string
256 264 ret = ""
257 265 begin
258 266 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
259 267 rescue Redmine::Scm::Adapters::CommandFailed => e
260 268 logger.error "scm: error during get version string: #{e.message}"
261 269 end
262 270 ret
263 271 end
264 272
265 273 def self.scm_available
266 274 ret = false
267 275 begin
268 276 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
269 277 rescue Redmine::Scm::Adapters::CommandFailed => e
270 278 logger.error "scm: error during get scm available: #{e.message}"
271 279 end
272 280 ret
273 281 end
274 282
275 283 private
276 284
277 285 def before_save
278 286 # Strips url and root_url
279 287 url.strip!
280 288 root_url.strip!
281 289 true
282 290 end
283 291
284 292 def clear_changesets
285 293 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
286 294 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
287 295 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
288 296 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
289 297 end
290 298 end
@@ -1,99 +1,98
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/bazaar_adapter'
19 19
20 20 class Repository::Bazaar < Repository
21 21 attr_protected :root_url
22 22 validates_presence_of :url, :log_encoding
23 23
24 24 ATTRIBUTE_KEY_NAMES = {
25 25 "url" => "Root directory",
26 "log_encoding" => "Commit messages encoding",
27 26 }
28 27 def self.human_attribute_name(attribute_key_name)
29 28 ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
30 29 end
31 30
32 31 def self.scm_adapter_class
33 32 Redmine::Scm::Adapters::BazaarAdapter
34 33 end
35 34
36 35 def self.scm_name
37 36 'Bazaar'
38 37 end
39 38
40 39 def entries(path=nil, identifier=nil)
41 40 entries = scm.entries(path, identifier)
42 41 if entries
43 42 entries.each do |e|
44 43 next if e.lastrev.revision.blank?
45 44 # Set the filesize unless browsing a specific revision
46 45 if identifier.nil? && e.is_file?
47 46 full_path = File.join(root_url, e.path)
48 47 e.size = File.stat(full_path).size if File.file?(full_path)
49 48 end
50 49 c = Change.find(:first,
51 50 :include => :changeset,
52 51 :conditions => ["#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", e.lastrev.revision, id],
53 52 :order => "#{Changeset.table_name}.revision DESC")
54 53 if c
55 54 e.lastrev.identifier = c.changeset.revision
56 55 e.lastrev.name = c.changeset.revision
57 56 e.lastrev.author = c.changeset.committer
58 57 end
59 58 end
60 59 end
61 60 end
62 61
63 62 def fetch_changesets
64 63 scm_info = scm.info
65 64 if scm_info
66 65 # latest revision found in database
67 66 db_revision = latest_changeset ? latest_changeset.revision.to_i : 0
68 67 # latest revision in the repository
69 68 scm_revision = scm_info.lastrev.identifier.to_i
70 69 if db_revision < scm_revision
71 70 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
72 71 identifier_from = db_revision + 1
73 72 while (identifier_from <= scm_revision)
74 73 # loads changesets by batches of 200
75 74 identifier_to = [identifier_from + 199, scm_revision].min
76 75 revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
77 76 transaction do
78 77 revisions.reverse_each do |revision|
79 78 changeset = Changeset.create(:repository => self,
80 79 :revision => revision.identifier,
81 80 :committer => revision.author,
82 81 :committed_on => revision.time,
83 82 :scmid => revision.scmid,
84 83 :comments => revision.message)
85 84
86 85 revision.paths.each do |change|
87 86 Change.create(:changeset => changeset,
88 87 :action => change[:action],
89 88 :path => change[:path],
90 89 :revision => change[:revision])
91 90 end
92 91 end
93 92 end unless revisions.nil?
94 93 identifier_from = identifier_to + 1
95 94 end
96 95 end
97 96 end
98 97 end
99 98 end
@@ -1,204 +1,203
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/cvs_adapter'
19 19 require 'digest/sha1'
20 20
21 21 class Repository::Cvs < Repository
22 22 validates_presence_of :url, :root_url, :log_encoding
23 23
24 24 ATTRIBUTE_KEY_NAMES = {
25 25 "root_url" => "CVSROOT",
26 26 "url" => "Module",
27 "log_encoding" => "Commit messages encoding",
28 27 }
29 28 def self.human_attribute_name(attribute_key_name)
30 29 ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
31 30 end
32 31
33 32 def self.scm_adapter_class
34 33 Redmine::Scm::Adapters::CvsAdapter
35 34 end
36 35
37 36 def self.scm_name
38 37 'CVS'
39 38 end
40 39
41 40 def entry(path=nil, identifier=nil)
42 41 rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
43 42 scm.entry(path, rev.nil? ? nil : rev.committed_on)
44 43 end
45 44
46 45 def entries(path=nil, identifier=nil)
47 46 rev = nil
48 47 if ! identifier.nil?
49 48 rev = changesets.find_by_revision(identifier)
50 49 return nil if rev.nil?
51 50 end
52 51 entries = scm.entries(path, rev.nil? ? nil : rev.committed_on)
53 52 if entries
54 53 entries.each() do |entry|
55 54 if ( ! entry.lastrev.nil? ) && ( ! entry.lastrev.revision.nil? )
56 55 change=changes.find_by_revision_and_path(
57 56 entry.lastrev.revision,
58 57 scm.with_leading_slash(entry.path) )
59 58 if change
60 59 entry.lastrev.identifier = change.changeset.revision
61 60 entry.lastrev.revision = change.changeset.revision
62 61 entry.lastrev.author = change.changeset.committer
63 62 # entry.lastrev.branch = change.branch
64 63 end
65 64 end
66 65 end
67 66 end
68 67 entries
69 68 end
70 69
71 70 def cat(path, identifier=nil)
72 71 rev = nil
73 72 if ! identifier.nil?
74 73 rev = changesets.find_by_revision(identifier)
75 74 return nil if rev.nil?
76 75 end
77 76 scm.cat(path, rev.nil? ? nil : rev.committed_on)
78 77 end
79 78
80 79 def annotate(path, identifier=nil)
81 80 rev = nil
82 81 if ! identifier.nil?
83 82 rev = changesets.find_by_revision(identifier)
84 83 return nil if rev.nil?
85 84 end
86 85 scm.annotate(path, rev.nil? ? nil : rev.committed_on)
87 86 end
88 87
89 88 def diff(path, rev, rev_to)
90 89 # convert rev to revision. CVS can't handle changesets here
91 90 diff=[]
92 91 changeset_from = changesets.find_by_revision(rev)
93 92 if rev_to.to_i > 0
94 93 changeset_to = changesets.find_by_revision(rev_to)
95 94 end
96 95 changeset_from.changes.each() do |change_from|
97 96 revision_from = nil
98 97 revision_to = nil
99 98 if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
100 99 revision_from = change_from.revision
101 100 end
102 101 if revision_from
103 102 if changeset_to
104 103 changeset_to.changes.each() do |change_to|
105 104 revision_to=change_to.revision if change_to.path==change_from.path
106 105 end
107 106 end
108 107 unless revision_to
109 108 revision_to=scm.get_previous_revision(revision_from)
110 109 end
111 110 file_diff = scm.diff(change_from.path, revision_from, revision_to)
112 111 diff = diff + file_diff unless file_diff.nil?
113 112 end
114 113 end
115 114 return diff
116 115 end
117 116
118 117 def fetch_changesets
119 118 # some nifty bits to introduce a commit-id with cvs
120 119 # natively cvs doesn't provide any kind of changesets,
121 120 # there is only a revision per file.
122 121 # we now take a guess using the author, the commitlog and the commit-date.
123 122
124 123 # last one is the next step to take. the commit-date is not equal for all
125 124 # commits in one changeset. cvs update the commit-date when the *,v file was touched. so
126 125 # we use a small delta here, to merge all changes belonging to _one_ changeset
127 126 time_delta = 10.seconds
128 127 fetch_since = latest_changeset ? latest_changeset.committed_on : nil
129 128 transaction do
130 129 tmp_rev_num = 1
131 130 scm.revisions('', fetch_since, nil, :log_encoding => repo_log_encoding) do |revision|
132 131 # only add the change to the database, if it doen't exists. the cvs log
133 132 # is not exclusive at all.
134 133 tmp_time = revision.time.clone
135 134 unless changes.find_by_path_and_revision(
136 135 scm.with_leading_slash(revision.paths[0][:path]),
137 136 revision.paths[0][:revision]
138 137 )
139 138 cmt = Changeset.normalize_comments(revision.message, repo_log_encoding)
140 139 author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding)
141 140 cs = changesets.find(
142 141 :first,
143 142 :conditions => {
144 143 :committed_on => tmp_time - time_delta .. tmp_time + time_delta,
145 144 :committer => author_utf8,
146 145 :comments => cmt
147 146 }
148 147 )
149 148 # create a new changeset....
150 149 unless cs
151 150 # we use a temporaray revision number here (just for inserting)
152 151 # later on, we calculate a continous positive number
153 152 tmp_time2 = tmp_time.clone.gmtime
154 153 branch = revision.paths[0][:branch]
155 154 scmid = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S")
156 155 cs = Changeset.create(:repository => self,
157 156 :revision => "tmp#{tmp_rev_num}",
158 157 :scmid => scmid,
159 158 :committer => revision.author,
160 159 :committed_on => tmp_time,
161 160 :comments => revision.message)
162 161 tmp_rev_num += 1
163 162 end
164 163 # convert CVS-File-States to internal Action-abbrevations
165 164 # default action is (M)odified
166 165 action = "M"
167 166 if revision.paths[0][:action] == "Exp" && revision.paths[0][:revision] == "1.1"
168 167 action = "A" # add-action always at first revision (= 1.1)
169 168 elsif revision.paths[0][:action] == "dead"
170 169 action = "D" # dead-state is similar to Delete
171 170 end
172 171 Change.create(
173 172 :changeset => cs,
174 173 :action => action,
175 174 :path => scm.with_leading_slash(revision.paths[0][:path]),
176 175 :revision => revision.paths[0][:revision],
177 176 :branch => revision.paths[0][:branch]
178 177 )
179 178 end
180 179 end
181 180
182 181 # Renumber new changesets in chronological order
183 182 changesets.find(
184 183 :all,
185 184 :order => 'committed_on ASC, id ASC',
186 185 :conditions => "revision LIKE 'tmp%'"
187 186 ).each do |changeset|
188 187 changeset.update_attribute :revision, next_revision_number
189 188 end
190 189 end # transaction
191 190 @current_revision_number = nil
192 191 end
193 192
194 193 private
195 194
196 195 # Returns the next revision number to assign to a CVS changeset
197 196 def next_revision_number
198 197 # Need to retrieve existing revision numbers to sort them as integers
199 198 sql = "SELECT revision FROM #{Changeset.table_name} "
200 199 sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'"
201 200 @current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0)
202 201 @current_revision_number += 1
203 202 end
204 203 end
@@ -1,113 +1,112
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/darcs_adapter'
19 19
20 20 class Repository::Darcs < Repository
21 21 validates_presence_of :url, :log_encoding
22 22
23 23 ATTRIBUTE_KEY_NAMES = {
24 24 "url" => "Root directory",
25 "log_encoding" => "Commit messages encoding",
26 25 }
27 26 def self.human_attribute_name(attribute_key_name)
28 27 ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
29 28 end
30 29
31 30 def self.scm_adapter_class
32 31 Redmine::Scm::Adapters::DarcsAdapter
33 32 end
34 33
35 34 def self.scm_name
36 35 'Darcs'
37 36 end
38 37
39 38 def supports_directory_revisions?
40 39 true
41 40 end
42 41
43 42 def entry(path=nil, identifier=nil)
44 43 patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
45 44 scm.entry(path, patch.nil? ? nil : patch.scmid)
46 45 end
47 46
48 47 def entries(path=nil, identifier=nil)
49 48 patch = nil
50 49 if ! identifier.nil?
51 50 patch = changesets.find_by_revision(identifier)
52 51 return nil if patch.nil?
53 52 end
54 53 entries = scm.entries(path, patch.nil? ? nil : patch.scmid)
55 54 if entries
56 55 entries.each do |entry|
57 56 # Search the DB for the entry's last change
58 57 if entry.lastrev && !entry.lastrev.scmid.blank?
59 58 changeset = changesets.find_by_scmid(entry.lastrev.scmid)
60 59 end
61 60 if changeset
62 61 entry.lastrev.identifier = changeset.revision
63 62 entry.lastrev.name = changeset.revision
64 63 entry.lastrev.time = changeset.committed_on
65 64 entry.lastrev.author = changeset.committer
66 65 end
67 66 end
68 67 end
69 68 entries
70 69 end
71 70
72 71 def cat(path, identifier=nil)
73 72 patch = identifier.nil? ? nil : changesets.find_by_revision(identifier.to_s)
74 73 scm.cat(path, patch.nil? ? nil : patch.scmid)
75 74 end
76 75
77 76 def diff(path, rev, rev_to)
78 77 patch_from = changesets.find_by_revision(rev)
79 78 return nil if patch_from.nil?
80 79 patch_to = changesets.find_by_revision(rev_to) if rev_to
81 80 if path.blank?
82 81 path = patch_from.changes.collect{|change| change.path}.join(' ')
83 82 end
84 83 patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil) : nil
85 84 end
86 85
87 86 def fetch_changesets
88 87 scm_info = scm.info
89 88 if scm_info
90 89 db_last_id = latest_changeset ? latest_changeset.scmid : nil
91 90 next_rev = latest_changeset ? latest_changeset.revision.to_i + 1 : 1
92 91 # latest revision in the repository
93 92 scm_revision = scm_info.lastrev.scmid
94 93 unless changesets.find_by_scmid(scm_revision)
95 94 revisions = scm.revisions('', db_last_id, nil, :with_path => true)
96 95 transaction do
97 96 revisions.reverse_each do |revision|
98 97 changeset = Changeset.create(:repository => self,
99 98 :revision => next_rev,
100 99 :scmid => revision.scmid,
101 100 :committer => revision.author,
102 101 :committed_on => revision.time,
103 102 :comments => revision.message)
104 103 revision.paths.each do |change|
105 104 changeset.create_change(change)
106 105 end
107 106 next_rev += 1
108 107 end if revisions
109 108 end
110 109 end
111 110 end
112 111 end
113 112 end
General Comments 0
You need to be logged in to leave comments. Login now