##// END OF EJS Templates
Fixed: Links to repository directories don't work (#1119)....
Jean-Philippe Lang -
r1350:a73f68a185df
parent child
Show More
@@ -1,307 +1,314
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 'SVG/Graph/Bar'
19 19 require 'SVG/Graph/BarHorizontal'
20 20 require 'digest/sha1'
21 21
22 22 class ChangesetNotFound < Exception; end
23 23 class InvalidRevisionParam < Exception; end
24 24
25 25 class RepositoriesController < ApplicationController
26 26 layout 'base'
27 27 menu_item :repository
28 28 before_filter :find_repository, :except => :edit
29 29 before_filter :find_project, :only => :edit
30 30 before_filter :authorize
31 31 accept_key_auth :revisions
32 32
33 33 def edit
34 34 @repository = @project.repository
35 35 if !@repository
36 36 @repository = Repository.factory(params[:repository_scm])
37 37 @repository.project = @project
38 38 end
39 39 if request.post?
40 40 @repository.attributes = params[:repository]
41 41 @repository.save
42 42 end
43 43 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
44 44 end
45 45
46 46 def destroy
47 47 @repository.destroy
48 48 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
49 49 end
50 50
51 51 def show
52 52 # check if new revisions have been committed in the repository
53 53 @repository.fetch_changesets if Setting.autofetch_changesets?
54 54 # root entries
55 55 @entries = @repository.entries('', @rev)
56 56 # latest changesets
57 57 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
58 58 show_error_not_found unless @entries || @changesets.any?
59 59 rescue Redmine::Scm::Adapters::CommandFailed => e
60 60 show_error_command_failed(e.message)
61 61 end
62 62
63 63 def browse
64 64 @entries = @repository.entries(@path, @rev)
65 65 if request.xhr?
66 66 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
67 67 else
68 show_error_not_found unless @entries
68 show_error_not_found and return unless @entries
69 render :action => 'browse'
69 70 end
70 71 rescue Redmine::Scm::Adapters::CommandFailed => e
71 72 show_error_command_failed(e.message)
72 73 end
73 74
74 75 def changes
75 76 @entry = @repository.scm.entry(@path, @rev)
76 77 show_error_not_found and return unless @entry
77 78 @changesets = @repository.changesets_for_path(@path)
78 79 rescue Redmine::Scm::Adapters::CommandFailed => e
79 80 show_error_command_failed(e.message)
80 81 end
81 82
82 83 def revisions
83 84 @changeset_count = @repository.changesets.count
84 85 @changeset_pages = Paginator.new self, @changeset_count,
85 86 per_page_option,
86 87 params['page']
87 88 @changesets = @repository.changesets.find(:all,
88 89 :limit => @changeset_pages.items_per_page,
89 90 :offset => @changeset_pages.current.offset)
90 91
91 92 respond_to do |format|
92 93 format.html { render :layout => false if request.xhr? }
93 94 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
94 95 end
95 96 end
96 97
97 98 def entry
99 @entry = @repository.scm.entry(@path, @rev)
100 show_error_not_found and return unless @entry
101
102 # If the entry is a dir, show the browser
103 browse and return if @entry.is_dir?
104
98 105 @content = @repository.scm.cat(@path, @rev)
99 106 show_error_not_found and return unless @content
100 107 if 'raw' == params[:format] || @content.is_binary_data?
101 108 # Force the download if it's a binary file
102 109 send_data @content, :filename => @path.split('/').last
103 110 else
104 111 # Prevent empty lines when displaying a file with Windows style eol
105 112 @content.gsub!("\r\n", "\n")
106 113 end
107 114 rescue Redmine::Scm::Adapters::CommandFailed => e
108 115 show_error_command_failed(e.message)
109 116 end
110 117
111 118 def annotate
112 119 @annotate = @repository.scm.annotate(@path, @rev)
113 120 render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
114 121 rescue Redmine::Scm::Adapters::CommandFailed => e
115 122 show_error_command_failed(e.message)
116 123 end
117 124
118 125 def revision
119 126 @changeset = @repository.changesets.find_by_revision(@rev)
120 127 raise ChangesetNotFound unless @changeset
121 128 @changes_count = @changeset.changes.size
122 129 @changes_pages = Paginator.new self, @changes_count, 150, params['page']
123 130 @changes = @changeset.changes.find(:all,
124 131 :limit => @changes_pages.items_per_page,
125 132 :offset => @changes_pages.current.offset)
126 133
127 134 respond_to do |format|
128 135 format.html
129 136 format.js {render :layout => false}
130 137 end
131 138 rescue ChangesetNotFound
132 139 show_error_not_found
133 140 rescue Redmine::Scm::Adapters::CommandFailed => e
134 141 show_error_command_failed(e.message)
135 142 end
136 143
137 144 def diff
138 145 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
139 146 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
140 147
141 148 # Save diff type as user preference
142 149 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
143 150 User.current.pref[:diff_type] = @diff_type
144 151 User.current.preference.save
145 152 end
146 153
147 154 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
148 155 unless read_fragment(@cache_key)
149 156 @diff = @repository.diff(@path, @rev, @rev_to, @diff_type)
150 157 show_error_not_found unless @diff
151 158 end
152 159 rescue Redmine::Scm::Adapters::CommandFailed => e
153 160 show_error_command_failed(e.message)
154 161 end
155 162
156 163 def stats
157 164 end
158 165
159 166 def graph
160 167 data = nil
161 168 case params[:graph]
162 169 when "commits_per_month"
163 170 data = graph_commits_per_month(@repository)
164 171 when "commits_per_author"
165 172 data = graph_commits_per_author(@repository)
166 173 end
167 174 if data
168 175 headers["Content-Type"] = "image/svg+xml"
169 176 send_data(data, :type => "image/svg+xml", :disposition => "inline")
170 177 else
171 178 render_404
172 179 end
173 180 end
174 181
175 182 private
176 183 def find_project
177 184 @project = Project.find(params[:id])
178 185 rescue ActiveRecord::RecordNotFound
179 186 render_404
180 187 end
181 188
182 189 REV_PARAM_RE = %r{^[a-f0-9]*$}
183 190
184 191 def find_repository
185 192 @project = Project.find(params[:id])
186 193 @repository = @project.repository
187 194 render_404 and return false unless @repository
188 195 @path = params[:path].join('/') unless params[:path].nil?
189 196 @path ||= ''
190 197 @rev = params[:rev]
191 198 @rev_to = params[:rev_to]
192 199 raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
193 200 rescue ActiveRecord::RecordNotFound
194 201 render_404
195 202 rescue InvalidRevisionParam
196 203 show_error_not_found
197 204 end
198 205
199 206 def show_error_not_found
200 207 render_error l(:error_scm_not_found)
201 208 end
202 209
203 210 def show_error_command_failed(msg)
204 211 render_error l(:error_scm_command_failed, msg)
205 212 end
206 213
207 214 def graph_commits_per_month(repository)
208 215 @date_to = Date.today
209 216 @date_from = @date_to << 11
210 217 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
211 218 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
212 219 commits_by_month = [0] * 12
213 220 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
214 221
215 222 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
216 223 changes_by_month = [0] * 12
217 224 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
218 225
219 226 fields = []
220 227 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
221 228 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
222 229
223 230 graph = SVG::Graph::Bar.new(
224 231 :height => 300,
225 232 :width => 500,
226 233 :fields => fields.reverse,
227 234 :stack => :side,
228 235 :scale_integers => true,
229 236 :step_x_labels => 2,
230 237 :show_data_values => false,
231 238 :graph_title => l(:label_commits_per_month),
232 239 :show_graph_title => true
233 240 )
234 241
235 242 graph.add_data(
236 243 :data => commits_by_month[0..11].reverse,
237 244 :title => l(:label_revision_plural)
238 245 )
239 246
240 247 graph.add_data(
241 248 :data => changes_by_month[0..11].reverse,
242 249 :title => l(:label_change_plural)
243 250 )
244 251
245 252 graph.burn
246 253 end
247 254
248 255 def graph_commits_per_author(repository)
249 256 commits_by_author = repository.changesets.count(:all, :group => :committer)
250 257 commits_by_author.sort! {|x, y| x.last <=> y.last}
251 258
252 259 changes_by_author = repository.changes.count(:all, :group => :committer)
253 260 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
254 261
255 262 fields = commits_by_author.collect {|r| r.first}
256 263 commits_data = commits_by_author.collect {|r| r.last}
257 264 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
258 265
259 266 fields = fields + [""]*(10 - fields.length) if fields.length<10
260 267 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
261 268 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
262 269
263 270 # Remove email adress in usernames
264 271 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
265 272
266 273 graph = SVG::Graph::BarHorizontal.new(
267 274 :height => 300,
268 275 :width => 500,
269 276 :fields => fields,
270 277 :stack => :side,
271 278 :scale_integers => true,
272 279 :show_data_values => false,
273 280 :rotate_y_labels => false,
274 281 :graph_title => l(:label_commits_per_author),
275 282 :show_graph_title => true
276 283 )
277 284
278 285 graph.add_data(
279 286 :data => commits_data,
280 287 :title => l(:label_revision_plural)
281 288 )
282 289
283 290 graph.add_data(
284 291 :data => changes_data,
285 292 :title => l(:label_change_plural)
286 293 )
287 294
288 295 graph.burn
289 296 end
290 297
291 298 end
292 299
293 300 class Date
294 301 def months_ago(date = Date.today)
295 302 (date.year - self.year)*12 + (date.month - self.month)
296 303 end
297 304
298 305 def weeks_ago(date = Date.today)
299 306 (date.year - self.year)*52 + (date.cweek - self.cweek)
300 307 end
301 308 end
302 309
303 310 class String
304 311 def with_leading_slash
305 312 starts_with?('/') ? self : "/#{self}"
306 313 end
307 314 end
@@ -1,386 +1,395
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 'cgi'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class CommandFailed < StandardError #:nodoc:
24 24 end
25 25
26 26 class AbstractAdapter #:nodoc:
27 27 def initialize(url, root_url=nil, login=nil, password=nil)
28 28 @url = url
29 29 @login = login if login && !login.empty?
30 30 @password = (password || "") if @login
31 31 @root_url = root_url.blank? ? retrieve_root_url : root_url
32 32 end
33 33
34 34 def adapter_name
35 35 'Abstract'
36 36 end
37 37
38 38 def supports_cat?
39 39 true
40 40 end
41 41
42 42 def supports_annotate?
43 43 respond_to?('annotate')
44 44 end
45 45
46 46 def root_url
47 47 @root_url
48 48 end
49 49
50 50 def url
51 51 @url
52 52 end
53 53
54 54 # get info about the svn repository
55 55 def info
56 56 return nil
57 57 end
58 58
59 59 # Returns the entry identified by path and revision identifier
60 60 # or nil if entry doesn't exist in the repository
61 61 def entry(path=nil, identifier=nil)
62 e = entries(path, identifier)
63 e ? e.first : nil
62 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
63 search_path = parts[0..-2].join('/')
64 search_name = parts[-1]
65 if search_path.blank? && search_name.blank?
66 # Root entry
67 Entry.new(:path => '', :kind => 'dir')
68 else
69 # Search for the entry in the parent directory
70 es = entries(search_path, identifier)
71 es ? es.detect {|e| e.name == search_name} : nil
72 end
64 73 end
65 74
66 75 # Returns an Entries collection
67 76 # or nil if the given path doesn't exist in the repository
68 77 def entries(path=nil, identifier=nil)
69 78 return nil
70 79 end
71 80
72 81 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
73 82 return nil
74 83 end
75 84
76 85 def diff(path, identifier_from, identifier_to=nil, type="inline")
77 86 return nil
78 87 end
79 88
80 89 def cat(path, identifier=nil)
81 90 return nil
82 91 end
83 92
84 93 def with_leading_slash(path)
85 94 path ||= ''
86 95 (path[0,1]!="/") ? "/#{path}" : path
87 96 end
88 97
89 98 def shell_quote(str)
90 99 if RUBY_PLATFORM =~ /mswin/
91 100 '"' + str.gsub(/"/, '\\"') + '"'
92 101 else
93 102 "'" + str.gsub(/'/, "'\"'\"'") + "'"
94 103 end
95 104 end
96 105
97 106 private
98 107 def retrieve_root_url
99 108 info = self.info
100 109 info ? info.root_url : nil
101 110 end
102 111
103 112 def target(path)
104 113 path ||= ''
105 114 base = path.match(/^\//) ? root_url : url
106 115 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
107 116 end
108 117
109 118 def logger
110 119 RAILS_DEFAULT_LOGGER
111 120 end
112 121
113 122 def shellout(cmd, &block)
114 123 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
115 124 begin
116 125 IO.popen(cmd, "r+") do |io|
117 126 io.close_write
118 127 block.call(io) if block_given?
119 128 end
120 129 rescue Errno::ENOENT => e
121 130 # The command failed, log it and re-raise
122 131 logger.error("SCM command failed: #{cmd}\n with: #{e.message}")
123 132 raise CommandFailed.new(e.message)
124 133 end
125 134 end
126 135 end
127 136
128 137 class Entries < Array
129 138 def sort_by_name
130 139 sort {|x,y|
131 140 if x.kind == y.kind
132 141 x.name <=> y.name
133 142 else
134 143 x.kind <=> y.kind
135 144 end
136 145 }
137 146 end
138 147
139 148 def revisions
140 149 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
141 150 end
142 151 end
143 152
144 153 class Info
145 154 attr_accessor :root_url, :lastrev
146 155 def initialize(attributes={})
147 156 self.root_url = attributes[:root_url] if attributes[:root_url]
148 157 self.lastrev = attributes[:lastrev]
149 158 end
150 159 end
151 160
152 161 class Entry
153 162 attr_accessor :name, :path, :kind, :size, :lastrev
154 163 def initialize(attributes={})
155 164 self.name = attributes[:name] if attributes[:name]
156 165 self.path = attributes[:path] if attributes[:path]
157 166 self.kind = attributes[:kind] if attributes[:kind]
158 167 self.size = attributes[:size].to_i if attributes[:size]
159 168 self.lastrev = attributes[:lastrev]
160 169 end
161 170
162 171 def is_file?
163 172 'file' == self.kind
164 173 end
165 174
166 175 def is_dir?
167 176 'dir' == self.kind
168 177 end
169 178
170 179 def is_text?
171 180 Redmine::MimeType.is_type?('text', name)
172 181 end
173 182 end
174 183
175 184 class Revisions < Array
176 185 def latest
177 186 sort {|x,y|
178 187 unless x.time.nil? or y.time.nil?
179 188 x.time <=> y.time
180 189 else
181 190 0
182 191 end
183 192 }.last
184 193 end
185 194 end
186 195
187 196 class Revision
188 197 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
189 198 def initialize(attributes={})
190 199 self.identifier = attributes[:identifier]
191 200 self.scmid = attributes[:scmid]
192 201 self.name = attributes[:name] || self.identifier
193 202 self.author = attributes[:author]
194 203 self.time = attributes[:time]
195 204 self.message = attributes[:message] || ""
196 205 self.paths = attributes[:paths]
197 206 self.revision = attributes[:revision]
198 207 self.branch = attributes[:branch]
199 208 end
200 209
201 210 end
202 211
203 212 # A line of Diff
204 213 class Diff
205 214 attr_accessor :nb_line_left
206 215 attr_accessor :line_left
207 216 attr_accessor :nb_line_right
208 217 attr_accessor :line_right
209 218 attr_accessor :type_diff_right
210 219 attr_accessor :type_diff_left
211 220
212 221 def initialize ()
213 222 self.nb_line_left = ''
214 223 self.nb_line_right = ''
215 224 self.line_left = ''
216 225 self.line_right = ''
217 226 self.type_diff_right = ''
218 227 self.type_diff_left = ''
219 228 end
220 229
221 230 def inspect
222 231 puts '### Start Line Diff ###'
223 232 puts self.nb_line_left
224 233 puts self.line_left
225 234 puts self.nb_line_right
226 235 puts self.line_right
227 236 end
228 237 end
229 238
230 239 class DiffTableList < Array
231 240 def initialize (diff, type="inline")
232 241 diff_table = DiffTable.new type
233 242 diff.each do |line|
234 243 if line =~ /^(---|\+\+\+) (.*)$/
235 244 self << diff_table if diff_table.length > 1
236 245 diff_table = DiffTable.new type
237 246 end
238 247 a = diff_table.add_line line
239 248 end
240 249 self << diff_table unless diff_table.empty?
241 250 self
242 251 end
243 252 end
244 253
245 254 # Class for create a Diff
246 255 class DiffTable < Hash
247 256 attr_reader :file_name, :line_num_l, :line_num_r
248 257
249 258 # Initialize with a Diff file and the type of Diff View
250 259 # The type view must be inline or sbs (side_by_side)
251 260 def initialize(type="inline")
252 261 @parsing = false
253 262 @nb_line = 1
254 263 @start = false
255 264 @before = 'same'
256 265 @second = true
257 266 @type = type
258 267 end
259 268
260 269 # Function for add a line of this Diff
261 270 def add_line(line)
262 271 unless @parsing
263 272 if line =~ /^(---|\+\+\+) (.*)$/
264 273 @file_name = $2
265 274 return false
266 275 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
267 276 @line_num_l = $5.to_i
268 277 @line_num_r = $2.to_i
269 278 @parsing = true
270 279 end
271 280 else
272 281 if line =~ /^[^\+\-\s@\\]/
273 282 self.delete(self.keys.sort.last)
274 283 @parsing = false
275 284 return false
276 285 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
277 286 @line_num_l = $5.to_i
278 287 @line_num_r = $2.to_i
279 288 else
280 289 @nb_line += 1 if parse_line(line, @type)
281 290 end
282 291 end
283 292 return true
284 293 end
285 294
286 295 def inspect
287 296 puts '### DIFF TABLE ###'
288 297 puts "file : #{file_name}"
289 298 self.each do |d|
290 299 d.inspect
291 300 end
292 301 end
293 302
294 303 private
295 304 # Test if is a Side By Side type
296 305 def sbs?(type, func)
297 306 if @start and type == "sbs"
298 307 if @before == func and @second
299 308 tmp_nb_line = @nb_line
300 309 self[tmp_nb_line] = Diff.new
301 310 else
302 311 @second = false
303 312 tmp_nb_line = @start
304 313 @start += 1
305 314 @nb_line -= 1
306 315 end
307 316 else
308 317 tmp_nb_line = @nb_line
309 318 @start = @nb_line
310 319 self[tmp_nb_line] = Diff.new
311 320 @second = true
312 321 end
313 322 unless self[tmp_nb_line]
314 323 @nb_line += 1
315 324 self[tmp_nb_line] = Diff.new
316 325 else
317 326 self[tmp_nb_line]
318 327 end
319 328 end
320 329
321 330 # Escape the HTML for the diff
322 331 def escapeHTML(line)
323 332 CGI.escapeHTML(line)
324 333 end
325 334
326 335 def parse_line(line, type="inline")
327 336 if line[0, 1] == "+"
328 337 diff = sbs? type, 'add'
329 338 @before = 'add'
330 339 diff.line_left = escapeHTML line[1..-1]
331 340 diff.nb_line_left = @line_num_l
332 341 diff.type_diff_left = 'diff_in'
333 342 @line_num_l += 1
334 343 true
335 344 elsif line[0, 1] == "-"
336 345 diff = sbs? type, 'remove'
337 346 @before = 'remove'
338 347 diff.line_right = escapeHTML line[1..-1]
339 348 diff.nb_line_right = @line_num_r
340 349 diff.type_diff_right = 'diff_out'
341 350 @line_num_r += 1
342 351 true
343 352 elsif line[0, 1] =~ /\s/
344 353 @before = 'same'
345 354 @start = false
346 355 diff = Diff.new
347 356 diff.line_right = escapeHTML line[1..-1]
348 357 diff.nb_line_right = @line_num_r
349 358 diff.line_left = escapeHTML line[1..-1]
350 359 diff.nb_line_left = @line_num_l
351 360 self[@nb_line] = diff
352 361 @line_num_l += 1
353 362 @line_num_r += 1
354 363 true
355 364 elsif line[0, 1] = "\\"
356 365 true
357 366 else
358 367 false
359 368 end
360 369 end
361 370 end
362 371
363 372 class Annotate
364 373 attr_reader :lines, :revisions
365 374
366 375 def initialize
367 376 @lines = []
368 377 @revisions = []
369 378 end
370 379
371 380 def add_line(line, revision)
372 381 @lines << line
373 382 @revisions << revision
374 383 end
375 384
376 385 def content
377 386 content = lines.join("\n")
378 387 end
379 388
380 389 def empty?
381 390 lines.empty?
382 391 end
383 392 end
384 393 end
385 394 end
386 395 end
@@ -1,197 +1,185
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/abstract_adapter'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class BazaarAdapter < AbstractAdapter
24 24
25 25 # Bazaar executable name
26 26 BZR_BIN = "bzr"
27 27
28 28 # Get info about the repository
29 29 def info
30 30 cmd = "#{BZR_BIN} revno #{target('')}"
31 31 info = nil
32 32 shellout(cmd) do |io|
33 33 if io.read =~ %r{^(\d+)$}
34 34 info = Info.new({:root_url => url,
35 35 :lastrev => Revision.new({
36 36 :identifier => $1
37 37 })
38 38 })
39 39 end
40 40 end
41 41 return nil if $? && $?.exitstatus != 0
42 42 info
43 43 rescue CommandFailed
44 44 return nil
45 45 end
46 46
47 # Returns the entry identified by path and revision identifier
48 # or nil if entry doesn't exist in the repository
49 def entry(path=nil, identifier=nil)
50 path ||= ''
51 parts = path.split(%r{[\/\\]}).select {|p| !p.blank?}
52 if parts.size > 0
53 parent = parts[0..-2].join('/')
54 entries = entries(parent, identifier)
55 entries ? entries.detect {|e| e.name == parts.last} : nil
56 end
57 end
58
59 47 # Returns an Entries collection
60 48 # or nil if the given path doesn't exist in the repository
61 49 def entries(path=nil, identifier=nil)
62 50 path ||= ''
63 51 entries = Entries.new
64 52 cmd = "#{BZR_BIN} ls -v --show-ids"
65 53 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
66 54 cmd << " #{target(path)}"
67 55 shellout(cmd) do |io|
68 56 prefix = "#{url}/#{path}".gsub('\\', '/')
69 57 logger.debug "PREFIX: #{prefix}"
70 58 re = %r{^V\s+#{Regexp.escape(prefix)}(\/?)([^\/]+)(\/?)\s+(\S+)$}
71 59 io.each_line do |line|
72 60 next unless line =~ re
73 61 entries << Entry.new({:name => $2.strip,
74 62 :path => ((path.empty? ? "" : "#{path}/") + $2.strip),
75 63 :kind => ($3.blank? ? 'file' : 'dir'),
76 64 :size => nil,
77 65 :lastrev => Revision.new(:revision => $4.strip)
78 66 })
79 67 end
80 68 end
81 69 return nil if $? && $?.exitstatus != 0
82 70 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
83 71 entries.sort_by_name
84 72 end
85 73
86 74 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
87 75 path ||= ''
88 76 identifier_from = 'last:1' unless identifier_from and identifier_from.to_i > 0
89 77 identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
90 78 revisions = Revisions.new
91 79 cmd = "#{BZR_BIN} log -v --show-ids -r#{identifier_to.to_i}..#{identifier_from} #{target(path)}"
92 80 shellout(cmd) do |io|
93 81 revision = nil
94 82 parsing = nil
95 83 io.each_line do |line|
96 84 if line =~ /^----/
97 85 revisions << revision if revision
98 86 revision = Revision.new(:paths => [], :message => '')
99 87 parsing = nil
100 88 else
101 89 next unless revision
102 90
103 91 if line =~ /^revno: (\d+)$/
104 92 revision.identifier = $1.to_i
105 93 elsif line =~ /^committer: (.+)$/
106 94 revision.author = $1.strip
107 95 elsif line =~ /^revision-id:(.+)$/
108 96 revision.scmid = $1.strip
109 97 elsif line =~ /^timestamp: (.+)$/
110 98 revision.time = Time.parse($1).localtime
111 99 elsif line =~ /^ -----/
112 100 # partial revisions
113 101 parsing = nil unless parsing == 'message'
114 102 elsif line =~ /^(message|added|modified|removed|renamed):/
115 103 parsing = $1
116 104 elsif line =~ /^ (.*)$/
117 105 if parsing == 'message'
118 106 revision.message << "#{$1}\n"
119 107 else
120 108 if $1 =~ /^(.*)\s+(\S+)$/
121 109 path = $1.strip
122 110 revid = $2
123 111 case parsing
124 112 when 'added'
125 113 revision.paths << {:action => 'A', :path => "/#{path}", :revision => revid}
126 114 when 'modified'
127 115 revision.paths << {:action => 'M', :path => "/#{path}", :revision => revid}
128 116 when 'removed'
129 117 revision.paths << {:action => 'D', :path => "/#{path}", :revision => revid}
130 118 when 'renamed'
131 119 new_path = path.split('=>').last
132 120 revision.paths << {:action => 'M', :path => "/#{new_path.strip}", :revision => revid} if new_path
133 121 end
134 122 end
135 123 end
136 124 else
137 125 parsing = nil
138 126 end
139 127 end
140 128 end
141 129 revisions << revision if revision
142 130 end
143 131 return nil if $? && $?.exitstatus != 0
144 132 revisions
145 133 end
146 134
147 135 def diff(path, identifier_from, identifier_to=nil, type="inline")
148 136 path ||= ''
149 137 if identifier_to
150 138 identifier_to = identifier_to.to_i
151 139 else
152 140 identifier_to = identifier_from.to_i - 1
153 141 end
154 142 cmd = "#{BZR_BIN} diff -r#{identifier_to}..#{identifier_from} #{target(path)}"
155 143 diff = []
156 144 shellout(cmd) do |io|
157 145 io.each_line do |line|
158 146 diff << line
159 147 end
160 148 end
161 149 #return nil if $? && $?.exitstatus != 0
162 150 DiffTableList.new diff, type
163 151 end
164 152
165 153 def cat(path, identifier=nil)
166 154 cmd = "#{BZR_BIN} cat"
167 155 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
168 156 cmd << " #{target(path)}"
169 157 cat = nil
170 158 shellout(cmd) do |io|
171 159 io.binmode
172 160 cat = io.read
173 161 end
174 162 return nil if $? && $?.exitstatus != 0
175 163 cat
176 164 end
177 165
178 166 def annotate(path, identifier=nil)
179 167 cmd = "#{BZR_BIN} annotate --all"
180 168 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
181 169 cmd << " #{target(path)}"
182 170 blame = Annotate.new
183 171 shellout(cmd) do |io|
184 172 author = nil
185 173 identifier = nil
186 174 io.each_line do |line|
187 175 next unless line =~ %r{^(\d+) ([^|]+)\| (.*)$}
188 176 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
189 177 end
190 178 end
191 179 return nil if $? && $?.exitstatus != 0
192 180 blame
193 181 end
194 182 end
195 183 end
196 184 end
197 185 end
@@ -1,363 +1,354
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/abstract_adapter'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class CvsAdapter < AbstractAdapter
24 24
25 25 # CVS executable name
26 26 CVS_BIN = "cvs"
27 27
28 28 # Guidelines for the input:
29 29 # url -> the project-path, relative to the cvsroot (eg. module name)
30 30 # root_url -> the good old, sometimes damned, CVSROOT
31 31 # login -> unnecessary
32 32 # password -> unnecessary too
33 33 def initialize(url, root_url=nil, login=nil, password=nil)
34 34 @url = url
35 35 @login = login if login && !login.empty?
36 36 @password = (password || "") if @login
37 37 #TODO: better Exception here (IllegalArgumentException)
38 38 raise CommandFailed if root_url.blank?
39 39 @root_url = root_url
40 40 end
41 41
42 42 def root_url
43 43 @root_url
44 44 end
45 45
46 46 def url
47 47 @url
48 48 end
49 49
50 50 def info
51 51 logger.debug "<cvs> info"
52 52 Info.new({:root_url => @root_url, :lastrev => nil})
53 53 end
54 54
55 55 def get_previous_revision(revision)
56 56 CvsRevisionHelper.new(revision).prevRev
57 57 end
58 58
59 # Returns the entry identified by path and revision identifier
60 # or nil if entry doesn't exist in the repository
61 # this method returns all revisions from one single SCM-Entry
62 def entry(path=nil, identifier="HEAD")
63 e = entries(path, identifier)
64 logger.debug("<cvs-result> #{e.first.inspect}") if e
65 e ? e.first : nil
66 end
67
68 59 # Returns an Entries collection
69 60 # or nil if the given path doesn't exist in the repository
70 61 # this method is used by the repository-browser (aka LIST)
71 62 def entries(path=nil, identifier=nil)
72 63 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
73 64 path_with_project="#{url}#{with_leading_slash(path)}"
74 65 entries = Entries.new
75 66 cmd = "#{CVS_BIN} -d #{root_url} rls -ed"
76 67 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
77 68 cmd << " #{path_with_project}"
78 69 shellout(cmd) do |io|
79 70 io.each_line(){|line|
80 71 fields=line.chop.split('/',-1)
81 72 logger.debug(">>InspectLine #{fields.inspect}")
82 73
83 74 if fields[0]!="D"
84 75 entries << Entry.new({:name => fields[-5],
85 76 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
86 77 :path => "#{path}/#{fields[-5]}",
87 78 :kind => 'file',
88 79 :size => nil,
89 80 :lastrev => Revision.new({
90 81 :revision => fields[-4],
91 82 :name => fields[-4],
92 83 :time => Time.parse(fields[-3]),
93 84 :author => ''
94 85 })
95 86 })
96 87 else
97 88 entries << Entry.new({:name => fields[1],
98 89 :path => "#{path}/#{fields[1]}",
99 90 :kind => 'dir',
100 91 :size => nil,
101 92 :lastrev => nil
102 93 })
103 94 end
104 95 }
105 96 end
106 97 return nil if $? && $?.exitstatus != 0
107 98 entries.sort_by_name
108 99 end
109 100
110 101 STARTLOG="----------------------------"
111 102 ENDLOG ="============================================================================="
112 103
113 104 # Returns all revisions found between identifier_from and identifier_to
114 105 # in the repository. both identifier have to be dates or nil.
115 106 # these method returns nothing but yield every result in block
116 107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
117 108 logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
118 109
119 110 path_with_project="#{url}#{with_leading_slash(path)}"
120 111 cmd = "#{CVS_BIN} -d #{root_url} rlog"
121 112 cmd << " -d\">#{time_to_cvstime(identifier_from)}\"" if identifier_from
122 113 cmd << " #{path_with_project}"
123 114 shellout(cmd) do |io|
124 115 state="entry_start"
125 116
126 117 commit_log=String.new
127 118 revision=nil
128 119 date=nil
129 120 author=nil
130 121 entry_path=nil
131 122 entry_name=nil
132 123 file_state=nil
133 124 branch_map=nil
134 125
135 126 io.each_line() do |line|
136 127
137 128 if state!="revision" && /^#{ENDLOG}/ =~ line
138 129 commit_log=String.new
139 130 revision=nil
140 131 state="entry_start"
141 132 end
142 133
143 134 if state=="entry_start"
144 135 branch_map=Hash.new
145 136 # gsub(/^:.*@[^:]+:\d*/, '') is here to remove :pserver:anonymous@foo.bar: string if present in the url
146 137 if /^RCS file: #{Regexp.escape(root_url.gsub(/^:.*@[^:]+:\d*/, ''))}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
147 138 entry_path = normalize_cvs_path($1)
148 139 entry_name = normalize_path(File.basename($1))
149 140 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
150 141 elsif /^head: (.+)$/ =~ line
151 142 entry_headRev = $1 #unless entry.nil?
152 143 elsif /^symbolic names:/ =~ line
153 144 state="symbolic" #unless entry.nil?
154 145 elsif /^#{STARTLOG}/ =~ line
155 146 commit_log=String.new
156 147 state="revision"
157 148 end
158 149 next
159 150 elsif state=="symbolic"
160 151 if /^(.*):\s(.*)/ =~ (line.strip)
161 152 branch_map[$1]=$2
162 153 else
163 154 state="tags"
164 155 next
165 156 end
166 157 elsif state=="tags"
167 158 if /^#{STARTLOG}/ =~ line
168 159 commit_log = ""
169 160 state="revision"
170 161 elsif /^#{ENDLOG}/ =~ line
171 162 state="head"
172 163 end
173 164 next
174 165 elsif state=="revision"
175 166 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
176 167 if revision
177 168
178 169 revHelper=CvsRevisionHelper.new(revision)
179 170 revBranch="HEAD"
180 171
181 172 branch_map.each() do |branch_name,branch_point|
182 173 if revHelper.is_in_branch_with_symbol(branch_point)
183 174 revBranch=branch_name
184 175 end
185 176 end
186 177
187 178 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
188 179
189 180 yield Revision.new({
190 181 :time => date,
191 182 :author => author,
192 183 :message=>commit_log.chomp,
193 184 :paths => [{
194 185 :revision => revision,
195 186 :branch=> revBranch,
196 187 :path=>entry_path,
197 188 :name=>entry_name,
198 189 :kind=>'file',
199 190 :action=>file_state
200 191 }]
201 192 })
202 193 end
203 194
204 195 commit_log=String.new
205 196 revision=nil
206 197
207 198 if /^#{ENDLOG}/ =~ line
208 199 state="entry_start"
209 200 end
210 201 next
211 202 end
212 203
213 204 if /^branches: (.+)$/ =~ line
214 205 #TODO: version.branch = $1
215 206 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
216 207 revision = $1
217 208 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
218 209 date = Time.parse($1)
219 210 author = /author: ([^;]+)/.match(line)[1]
220 211 file_state = /state: ([^;]+)/.match(line)[1]
221 212 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
222 213 # useful for stats or something else
223 214 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
224 215 # unless linechanges.nil?
225 216 # version.line_plus = linechanges[1]
226 217 # version.line_minus = linechanges[2]
227 218 # else
228 219 # version.line_plus = 0
229 220 # version.line_minus = 0
230 221 # end
231 222 else
232 223 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
233 224 end
234 225 end
235 226 end
236 227 end
237 228 end
238 229
239 230 def diff(path, identifier_from, identifier_to=nil, type="inline")
240 231 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
241 232 path_with_project="#{url}#{with_leading_slash(path)}"
242 233 cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{path_with_project}"
243 234 diff = []
244 235 shellout(cmd) do |io|
245 236 io.each_line do |line|
246 237 diff << line
247 238 end
248 239 end
249 240 return nil if $? && $?.exitstatus != 0
250 241 DiffTableList.new diff, type
251 242 end
252 243
253 244 def cat(path, identifier=nil)
254 245 identifier = (identifier) ? identifier : "HEAD"
255 246 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
256 247 path_with_project="#{url}#{with_leading_slash(path)}"
257 248 cmd = "#{CVS_BIN} -d #{root_url} co -r#{identifier} -p #{path_with_project}"
258 249 cat = nil
259 250 shellout(cmd) do |io|
260 251 cat = io.read
261 252 end
262 253 return nil if $? && $?.exitstatus != 0
263 254 cat
264 255 end
265 256
266 257 def annotate(path, identifier=nil)
267 258 identifier = (identifier) ? identifier : "HEAD"
268 259 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
269 260 path_with_project="#{url}#{with_leading_slash(path)}"
270 261 cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{path_with_project}"
271 262 blame = Annotate.new
272 263 shellout(cmd) do |io|
273 264 io.each_line do |line|
274 265 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
275 266 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
276 267 end
277 268 end
278 269 return nil if $? && $?.exitstatus != 0
279 270 blame
280 271 end
281 272
282 273 private
283 274
284 275 # convert a date/time into the CVS-format
285 276 def time_to_cvstime(time)
286 277 return nil if time.nil?
287 278 unless time.kind_of? Time
288 279 time = Time.parse(time)
289 280 end
290 281 return time.strftime("%Y-%m-%d %H:%M:%S")
291 282 end
292 283
293 284 def normalize_cvs_path(path)
294 285 normalize_path(path.gsub(/Attic\//,''))
295 286 end
296 287
297 288 def normalize_path(path)
298 289 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
299 290 end
300 291 end
301 292
302 293 class CvsRevisionHelper
303 294 attr_accessor :complete_rev, :revision, :base, :branchid
304 295
305 296 def initialize(complete_rev)
306 297 @complete_rev = complete_rev
307 298 parseRevision()
308 299 end
309 300
310 301 def branchPoint
311 302 return @base
312 303 end
313 304
314 305 def branchVersion
315 306 if isBranchRevision
316 307 return @base+"."+@branchid
317 308 end
318 309 return @base
319 310 end
320 311
321 312 def isBranchRevision
322 313 !@branchid.nil?
323 314 end
324 315
325 316 def prevRev
326 317 unless @revision==0
327 318 return buildRevision(@revision-1)
328 319 end
329 320 return buildRevision(@revision)
330 321 end
331 322
332 323 def is_in_branch_with_symbol(branch_symbol)
333 324 bpieces=branch_symbol.split(".")
334 325 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
335 326 return (branchVersion==branch_start)
336 327 end
337 328
338 329 private
339 330 def buildRevision(rev)
340 331 if rev== 0
341 332 @base
342 333 elsif @branchid.nil?
343 334 @base+"."+rev.to_s
344 335 else
345 336 @base+"."+@branchid+"."+rev.to_s
346 337 end
347 338 end
348 339
349 340 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
350 341 def parseRevision()
351 342 pieces=@complete_rev.split(".")
352 343 @revision=pieces.last.to_i
353 344 baseSize=1
354 345 baseSize+=(pieces.size/2)
355 346 @base=pieces[0..-baseSize].join(".")
356 347 if baseSize > 2
357 348 @branchid=pieces[-2]
358 349 end
359 350 end
360 351 end
361 352 end
362 353 end
363 354 end
@@ -1,163 +1,156
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/abstract_adapter'
19 19 require 'rexml/document'
20 20
21 21 module Redmine
22 22 module Scm
23 23 module Adapters
24 24 class DarcsAdapter < AbstractAdapter
25 25 # Darcs executable name
26 26 DARCS_BIN = "darcs"
27 27
28 28 def initialize(url, root_url=nil, login=nil, password=nil)
29 29 @url = url
30 30 @root_url = url
31 31 end
32 32
33 33 def supports_cat?
34 34 false
35 35 end
36 36
37 37 # Get info about the svn repository
38 38 def info
39 39 rev = revisions(nil,nil,nil,{:limit => 1})
40 40 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
41 41 end
42 42
43 # Returns the entry identified by path and revision identifier
44 # or nil if entry doesn't exist in the repository
45 def entry(path=nil, identifier=nil)
46 e = entries(path, identifier)
47 e ? e.first : nil
48 end
49
50 43 # Returns an Entries collection
51 44 # or nil if the given path doesn't exist in the repository
52 45 def entries(path=nil, identifier=nil)
53 46 path_prefix = (path.blank? ? '' : "#{path}/")
54 47 path = '.' if path.blank?
55 48 entries = Entries.new
56 49 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output"
57 50 cmd << " --match \"hash #{identifier}\"" if identifier
58 51 cmd << " #{path}"
59 52 shellout(cmd) do |io|
60 53 begin
61 54 doc = REXML::Document.new(io)
62 55 if doc.root.name == 'directory'
63 56 doc.elements.each('directory/*') do |element|
64 57 next unless ['file', 'directory'].include? element.name
65 58 entries << entry_from_xml(element, path_prefix)
66 59 end
67 60 elsif doc.root.name == 'file'
68 61 entries << entry_from_xml(doc.root, path_prefix)
69 62 end
70 63 rescue
71 64 end
72 65 end
73 66 return nil if $? && $?.exitstatus != 0
74 67 entries.sort_by_name
75 68 end
76 69
77 70 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
78 71 path = '.' if path.blank?
79 72 revisions = Revisions.new
80 73 cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
81 74 cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from
82 75 cmd << " --last #{options[:limit].to_i}" if options[:limit]
83 76 shellout(cmd) do |io|
84 77 begin
85 78 doc = REXML::Document.new(io)
86 79 doc.elements.each("changelog/patch") do |patch|
87 80 message = patch.elements['name'].text
88 81 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
89 82 revisions << Revision.new({:identifier => nil,
90 83 :author => patch.attributes['author'],
91 84 :scmid => patch.attributes['hash'],
92 85 :time => Time.parse(patch.attributes['local_date']),
93 86 :message => message,
94 87 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
95 88 })
96 89 end
97 90 rescue
98 91 end
99 92 end
100 93 return nil if $? && $?.exitstatus != 0
101 94 revisions
102 95 end
103 96
104 97 def diff(path, identifier_from, identifier_to=nil, type="inline")
105 98 path = '*' if path.blank?
106 99 cmd = "#{DARCS_BIN} diff --repodir #{@url}"
107 100 if identifier_to.nil?
108 101 cmd << " --match \"hash #{identifier_from}\""
109 102 else
110 103 cmd << " --to-match \"hash #{identifier_from}\""
111 104 cmd << " --from-match \"hash #{identifier_to}\""
112 105 end
113 106 cmd << " -u #{path}"
114 107 diff = []
115 108 shellout(cmd) do |io|
116 109 io.each_line do |line|
117 110 diff << line
118 111 end
119 112 end
120 113 return nil if $? && $?.exitstatus != 0
121 114 DiffTableList.new diff, type
122 115 end
123 116
124 117 private
125 118
126 119 def entry_from_xml(element, path_prefix)
127 120 Entry.new({:name => element.attributes['name'],
128 121 :path => path_prefix + element.attributes['name'],
129 122 :kind => element.name == 'file' ? 'file' : 'dir',
130 123 :size => nil,
131 124 :lastrev => Revision.new({
132 125 :identifier => nil,
133 126 :scmid => element.elements['modified'].elements['patch'].attributes['hash']
134 127 })
135 128 })
136 129 end
137 130
138 131 # Retrieve changed paths for a single patch
139 132 def get_paths_for_patch(hash)
140 133 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
141 134 cmd << " --match \"hash #{hash}\" "
142 135 paths = []
143 136 shellout(cmd) do |io|
144 137 begin
145 138 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
146 139 # A root element is added so that REXML doesn't raise an error
147 140 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
148 141 doc.elements.each('fake_root/summary/*') do |modif|
149 142 paths << {:action => modif.name[0,1].upcase,
150 143 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
151 144 }
152 145 end
153 146 rescue
154 147 end
155 148 end
156 149 paths
157 150 rescue CommandFailed
158 151 paths
159 152 end
160 153 end
161 154 end
162 155 end
163 156 end
@@ -1,264 +1,256
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/abstract_adapter'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class GitAdapter < AbstractAdapter
24 24
25 25 # Git executable name
26 26 GIT_BIN = "git"
27 27
28 28 # Get the revision of a particuliar file
29 29 def get_rev (rev,path)
30 30 cmd="git --git-dir #{target('')} show #{shell_quote rev} -- #{shell_quote path}" if rev!='latest' and (! rev.nil?)
31 31 cmd="git --git-dir #{target('')} log -1 master -- #{shell_quote path}" if
32 32 rev=='latest' or rev.nil?
33 33 rev=[]
34 34 i=0
35 35 shellout(cmd) do |io|
36 36 files=[]
37 37 changeset = {}
38 38 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
39 39
40 40 io.each_line do |line|
41 41 if line =~ /^commit ([0-9a-f]{40})$/
42 42 key = "commit"
43 43 value = $1
44 44 if (parsing_descr == 1 || parsing_descr == 2)
45 45 parsing_descr = 0
46 46 rev = Revision.new({:identifier => changeset[:commit],
47 47 :scmid => changeset[:commit],
48 48 :author => changeset[:author],
49 49 :time => Time.parse(changeset[:date]),
50 50 :message => changeset[:description],
51 51 :paths => files
52 52 })
53 53 changeset = {}
54 54 files = []
55 55 end
56 56 changeset[:commit] = $1
57 57 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
58 58 key = $1
59 59 value = $2
60 60 if key == "Author"
61 61 changeset[:author] = value
62 62 elsif key == "Date"
63 63 changeset[:date] = value
64 64 end
65 65 elsif (parsing_descr == 0) && line.chomp.to_s == ""
66 66 parsing_descr = 1
67 67 changeset[:description] = ""
68 68 elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
69 69 parsing_descr = 2
70 70 fileaction = $1
71 71 filepath = $2
72 72 files << {:action => fileaction, :path => filepath}
73 73 elsif (parsing_descr == 1) && line.chomp.to_s == ""
74 74 parsing_descr = 2
75 75 elsif (parsing_descr == 1)
76 76 changeset[:description] << line
77 77 end
78 78 end
79 79 rev = Revision.new({:identifier => changeset[:commit],
80 80 :scmid => changeset[:commit],
81 81 :author => changeset[:author],
82 82 :time => (changeset[:date] ? Time.parse(changeset[:date]) : nil),
83 83 :message => changeset[:description],
84 84 :paths => files
85 85 })
86 86
87 87 end
88 88
89 89 get_rev('latest',path) if rev == []
90 90
91 91 return nil if $? && $?.exitstatus != 0
92 92 return rev
93 93 end
94 94
95 95
96 96 def info
97 97 revs = revisions(url,nil,nil,{:limit => 1})
98 98 if revs && revs.any?
99 99 Info.new(:root_url => url, :lastrev => revs.first)
100 100 else
101 101 nil
102 102 end
103 103 rescue Errno::ENOENT => e
104 104 return nil
105 105 end
106 106
107 107 def entries(path=nil, identifier=nil)
108 108 path ||= ''
109 109 entries = Entries.new
110 110 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
111 111 cmd << shell_quote("HEAD:" + path) if identifier.nil?
112 112 cmd << shell_quote(identifier + ":" + path) if identifier
113 113 shellout(cmd) do |io|
114 114 io.each_line do |line|
115 115 e = line.chomp.to_s
116 116 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/
117 117 type = $1
118 118 sha = $2
119 119 size = $3
120 120 name = $4
121 121 entries << Entry.new({:name => name,
122 122 :path => (path.empty? ? name : "#{path}/#{name}"),
123 123 :kind => ((type == "tree") ? 'dir' : 'file'),
124 124 :size => ((type == "tree") ? nil : size),
125 125 :lastrev => get_rev(identifier,(path.empty? ? name : "#{path}/#{name}"))
126 126
127 127 }) unless entries.detect{|entry| entry.name == name}
128 128 end
129 129 end
130 130 end
131 131 return nil if $? && $?.exitstatus != 0
132 132 entries.sort_by_name
133 133 end
134 134
135 def entry(path=nil, identifier=nil)
136 path ||= ''
137 search_path = path.split('/')[0..-2].join('/')
138 entry_name = path.split('/').last
139 e = entries(search_path, identifier)
140 e ? e.detect{|entry| entry.name == entry_name} : nil
141 end
142
143 135 def revisions(path, identifier_from, identifier_to, options={})
144 136 revisions = Revisions.new
145 137 cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw "
146 138 cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit]
147 139 cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
148 140 cmd << " #{shell_quote identifier_to} " if identifier_to
149 141 #cmd << " HEAD " if !identifier_to
150 142 shellout(cmd) do |io|
151 143 files=[]
152 144 changeset = {}
153 145 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
154 146 revno = 1
155 147
156 148 io.each_line do |line|
157 149 if line =~ /^commit ([0-9a-f]{40})$/
158 150 key = "commit"
159 151 value = $1
160 152 if (parsing_descr == 1 || parsing_descr == 2)
161 153 parsing_descr = 0
162 154 revisions << Revision.new({:identifier => changeset[:commit],
163 155 :scmid => changeset[:commit],
164 156 :author => changeset[:author],
165 157 :time => Time.parse(changeset[:date]),
166 158 :message => changeset[:description],
167 159 :paths => files
168 160 })
169 161 changeset = {}
170 162 files = []
171 163 revno = revno + 1
172 164 end
173 165 changeset[:commit] = $1
174 166 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
175 167 key = $1
176 168 value = $2
177 169 if key == "Author"
178 170 changeset[:author] = value
179 171 elsif key == "Date"
180 172 changeset[:date] = value
181 173 end
182 174 elsif (parsing_descr == 0) && line.chomp.to_s == ""
183 175 parsing_descr = 1
184 176 changeset[:description] = ""
185 177 elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
186 178 parsing_descr = 2
187 179 fileaction = $1
188 180 filepath = $2
189 181 files << {:action => fileaction, :path => filepath}
190 182 elsif (parsing_descr == 1) && line.chomp.to_s == ""
191 183 parsing_descr = 2
192 184 elsif (parsing_descr == 1)
193 185 changeset[:description] << line[4..-1]
194 186 end
195 187 end
196 188
197 189 revisions << Revision.new({:identifier => changeset[:commit],
198 190 :scmid => changeset[:commit],
199 191 :author => changeset[:author],
200 192 :time => Time.parse(changeset[:date]),
201 193 :message => changeset[:description],
202 194 :paths => files
203 195 }) if changeset[:commit]
204 196
205 197 end
206 198
207 199 return nil if $? && $?.exitstatus != 0
208 200 revisions
209 201 end
210 202
211 203 def diff(path, identifier_from, identifier_to=nil, type="inline")
212 204 path ||= ''
213 205 if !identifier_to
214 206 identifier_to = nil
215 207 end
216 208
217 209 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}" if identifier_to.nil?
218 210 cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}" if !identifier_to.nil?
219 211 cmd << " -- #{shell_quote path}" unless path.empty?
220 212 diff = []
221 213 shellout(cmd) do |io|
222 214 io.each_line do |line|
223 215 diff << line
224 216 end
225 217 end
226 218 return nil if $? && $?.exitstatus != 0
227 219 DiffTableList.new diff, type
228 220 end
229 221
230 222 def annotate(path, identifier=nil)
231 223 identifier = 'HEAD' if identifier.blank?
232 224 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -l #{shell_quote identifier} -- #{shell_quote path}"
233 225 blame = Annotate.new
234 226 content = nil
235 227 shellout(cmd) { |io| io.binmode; content = io.read }
236 228 return nil if $? && $?.exitstatus != 0
237 229 # git annotates binary files
238 230 return nil if content.is_binary_data?
239 231 content.split("\n").each do |line|
240 232 next unless line =~ /([0-9a-f]{39,40})\s\((\w*)[^\)]*\)(.*)/
241 233 blame.add_line($3.rstrip, Revision.new(:identifier => $1, :author => $2.strip))
242 234 end
243 235 blame
244 236 end
245 237
246 238 def cat(path, identifier=nil)
247 239 if identifier.nil?
248 240 identifier = 'HEAD'
249 241 end
250 242 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}"
251 243 cat = nil
252 244 shellout(cmd) do |io|
253 245 io.binmode
254 246 cat = io.read
255 247 end
256 248 return nil if $? && $?.exitstatus != 0
257 249 cat
258 250 end
259 251 end
260 252 end
261 253 end
262 254
263 255 end
264 256
@@ -1,207 +1,199
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/abstract_adapter'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class MercurialAdapter < AbstractAdapter
24 24
25 25 # Mercurial executable name
26 26 HG_BIN = "hg"
27 27
28 28 def info
29 29 cmd = "#{HG_BIN} -R #{target('')} root"
30 30 root_url = nil
31 31 shellout(cmd) do |io|
32 32 root_url = io.gets
33 33 end
34 34 return nil if $? && $?.exitstatus != 0
35 35 info = Info.new({:root_url => root_url.chomp,
36 36 :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
37 37 })
38 38 info
39 39 rescue CommandFailed
40 40 return nil
41 41 end
42 42
43 43 def entries(path=nil, identifier=nil)
44 44 path ||= ''
45 45 entries = Entries.new
46 46 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target(path)} locate"
47 47 cmd << " -r #{identifier.to_i}" if identifier
48 48 cmd << " " + shell_quote('glob:**')
49 49 shellout(cmd) do |io|
50 50 io.each_line do |line|
51 51 e = line.chomp.split(%r{[\/\\]})
52 52 entries << Entry.new({:name => e.first,
53 53 :path => (path.empty? ? e.first : "#{path}/#{e.first}"),
54 54 :kind => (e.size > 1 ? 'dir' : 'file'),
55 55 :lastrev => Revision.new
56 56 }) unless entries.detect{|entry| entry.name == e.first}
57 57 end
58 58 end
59 59 return nil if $? && $?.exitstatus != 0
60 60 entries.sort_by_name
61 61 end
62 62
63 def entry(path=nil, identifier=nil)
64 path ||= ''
65 search_path = path.split('/')[0..-2].join('/')
66 entry_name = path.split('/').last
67 e = entries(search_path, identifier)
68 e ? e.detect{|entry| entry.name == entry_name} : nil
69 end
70
71 63 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
72 64 revisions = Revisions.new
73 65 cmd = "#{HG_BIN} -v --encoding utf8 -R #{target('')} log"
74 66 if identifier_from && identifier_to
75 67 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
76 68 elsif identifier_from
77 69 cmd << " -r #{identifier_from.to_i}:"
78 70 end
79 71 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
80 72 shellout(cmd) do |io|
81 73 changeset = {}
82 74 parsing_descr = false
83 75 line_feeds = 0
84 76
85 77 io.each_line do |line|
86 78 if line =~ /^(\w+):\s*(.*)$/
87 79 key = $1
88 80 value = $2
89 81 if parsing_descr && line_feeds > 1
90 82 parsing_descr = false
91 83 revisions << build_revision_from_changeset(changeset)
92 84 changeset = {}
93 85 end
94 86 if !parsing_descr
95 87 changeset.store key.to_sym, value
96 88 if $1 == "description"
97 89 parsing_descr = true
98 90 line_feeds = 0
99 91 next
100 92 end
101 93 end
102 94 end
103 95 if parsing_descr
104 96 changeset[:description] << line
105 97 line_feeds += 1 if line.chomp.empty?
106 98 end
107 99 end
108 100 # Add the last changeset if there is one left
109 101 revisions << build_revision_from_changeset(changeset) if changeset[:date]
110 102 end
111 103 return nil if $? && $?.exitstatus != 0
112 104 revisions
113 105 end
114 106
115 107 def diff(path, identifier_from, identifier_to=nil, type="inline")
116 108 path ||= ''
117 109 if identifier_to
118 110 identifier_to = identifier_to.to_i
119 111 else
120 112 identifier_to = identifier_from.to_i - 1
121 113 end
122 114 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
123 115 cmd << " -I #{target(path)}" unless path.empty?
124 116 diff = []
125 117 shellout(cmd) do |io|
126 118 io.each_line do |line|
127 119 diff << line
128 120 end
129 121 end
130 122 return nil if $? && $?.exitstatus != 0
131 123 DiffTableList.new diff, type
132 124 end
133 125
134 126 def cat(path, identifier=nil)
135 127 cmd = "#{HG_BIN} -R #{target('')} cat"
136 128 cmd << " -r #{identifier.to_i}" if identifier
137 129 cmd << " #{target(path)}"
138 130 cat = nil
139 131 shellout(cmd) do |io|
140 132 io.binmode
141 133 cat = io.read
142 134 end
143 135 return nil if $? && $?.exitstatus != 0
144 136 cat
145 137 end
146 138
147 139 def annotate(path, identifier=nil)
148 140 path ||= ''
149 141 cmd = "#{HG_BIN} -R #{target('')}"
150 142 cmd << " annotate -n -u"
151 143 cmd << " -r #{identifier.to_i}" if identifier
152 144 cmd << " #{target(path)}"
153 145 blame = Annotate.new
154 146 shellout(cmd) do |io|
155 147 io.each_line do |line|
156 148 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
157 149 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
158 150 end
159 151 end
160 152 return nil if $? && $?.exitstatus != 0
161 153 blame
162 154 end
163 155
164 156 private
165 157
166 158 # Builds a revision objet from the changeset returned by hg command
167 159 def build_revision_from_changeset(changeset)
168 160 rev_id = changeset[:changeset].to_s.split(':').first.to_i
169 161
170 162 # Changes
171 163 paths = (rev_id == 0) ?
172 164 # Can't get changes for revision 0 with hg status
173 165 changeset[:files].to_s.split.collect{|path| {:action => 'A', :path => "/#{path}"}} :
174 166 status(rev_id)
175 167
176 168 Revision.new({:identifier => rev_id,
177 169 :scmid => changeset[:changeset].to_s.split(':').last,
178 170 :author => changeset[:user],
179 171 :time => Time.parse(changeset[:date]),
180 172 :message => changeset[:description],
181 173 :paths => paths
182 174 })
183 175 end
184 176
185 177 # Returns the file changes for a given revision
186 178 def status(rev_id)
187 179 cmd = "#{HG_BIN} -R #{target('')} status --rev #{rev_id.to_i - 1}:#{rev_id.to_i}"
188 180 result = []
189 181 shellout(cmd) do |io|
190 182 io.each_line do |line|
191 183 action, file = line.chomp.split
192 184 next unless action && file
193 185 file.gsub!("\\", "/")
194 186 case action
195 187 when 'R'
196 188 result << { :action => 'D' , :path => "/#{file}" }
197 189 else
198 190 result << { :action => action, :path => "/#{file}" }
199 191 end
200 192 end
201 193 end
202 194 result
203 195 end
204 196 end
205 197 end
206 198 end
207 199 end
@@ -1,191 +1,184
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/abstract_adapter'
19 19 require 'rexml/document'
20 20
21 21 module Redmine
22 22 module Scm
23 23 module Adapters
24 24 class SubversionAdapter < AbstractAdapter
25 25
26 26 # SVN executable name
27 27 SVN_BIN = "svn"
28 28
29 29 # Get info about the svn repository
30 30 def info
31 31 cmd = "#{SVN_BIN} info --xml #{target('')}"
32 32 cmd << credentials_string
33 33 info = nil
34 34 shellout(cmd) do |io|
35 35 begin
36 36 doc = REXML::Document.new(io)
37 37 #root_url = doc.elements["info/entry/repository/root"].text
38 38 info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text,
39 39 :lastrev => Revision.new({
40 40 :identifier => doc.elements["info/entry/commit"].attributes['revision'],
41 41 :time => Time.parse(doc.elements["info/entry/commit/date"].text).localtime,
42 42 :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "")
43 43 })
44 44 })
45 45 rescue
46 46 end
47 47 end
48 48 return nil if $? && $?.exitstatus != 0
49 49 info
50 50 rescue CommandFailed
51 51 return nil
52 52 end
53 53
54 # Returns the entry identified by path and revision identifier
55 # or nil if entry doesn't exist in the repository
56 def entry(path=nil, identifier=nil)
57 e = entries(path, identifier)
58 e ? e.first : nil
59 end
60
61 54 # Returns an Entries collection
62 55 # or nil if the given path doesn't exist in the repository
63 56 def entries(path=nil, identifier=nil)
64 57 path ||= ''
65 58 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
66 59 entries = Entries.new
67 60 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
68 61 cmd << credentials_string
69 62 shellout(cmd) do |io|
70 63 output = io.read
71 64 begin
72 65 doc = REXML::Document.new(output)
73 66 doc.elements.each("lists/list/entry") do |entry|
74 67 entries << Entry.new({:name => entry.elements['name'].text,
75 68 :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
76 69 :kind => entry.attributes['kind'],
77 70 :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
78 71 :lastrev => Revision.new({
79 72 :identifier => entry.elements['commit'].attributes['revision'],
80 73 :time => Time.parse(entry.elements['commit'].elements['date'].text).localtime,
81 74 :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "")
82 75 })
83 76 })
84 77 end
85 78 rescue Exception => e
86 79 logger.error("Error parsing svn output: #{e.message}")
87 80 logger.error("Output was:\n #{output}")
88 81 end
89 82 end
90 83 return nil if $? && $?.exitstatus != 0
91 84 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
92 85 entries.sort_by_name
93 86 end
94 87
95 88 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
96 89 path ||= ''
97 90 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
98 91 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
99 92 revisions = Revisions.new
100 93 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
101 94 cmd << credentials_string
102 95 cmd << " --verbose " if options[:with_paths]
103 96 cmd << ' ' + target(path)
104 97 shellout(cmd) do |io|
105 98 begin
106 99 doc = REXML::Document.new(io)
107 100 doc.elements.each("log/logentry") do |logentry|
108 101 paths = []
109 102 logentry.elements.each("paths/path") do |path|
110 103 paths << {:action => path.attributes['action'],
111 104 :path => path.text,
112 105 :from_path => path.attributes['copyfrom-path'],
113 106 :from_revision => path.attributes['copyfrom-rev']
114 107 }
115 108 end
116 109 paths.sort! { |x,y| x[:path] <=> y[:path] }
117 110
118 111 revisions << Revision.new({:identifier => logentry.attributes['revision'],
119 112 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
120 113 :time => Time.parse(logentry.elements['date'].text).localtime,
121 114 :message => logentry.elements['msg'].text,
122 115 :paths => paths
123 116 })
124 117 end
125 118 rescue
126 119 end
127 120 end
128 121 return nil if $? && $?.exitstatus != 0
129 122 revisions
130 123 end
131 124
132 125 def diff(path, identifier_from, identifier_to=nil, type="inline")
133 126 path ||= ''
134 127 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
135 128 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
136 129
137 130 cmd = "#{SVN_BIN} diff -r "
138 131 cmd << "#{identifier_to}:"
139 132 cmd << "#{identifier_from}"
140 133 cmd << " #{target(path)}@#{identifier_from}"
141 134 cmd << credentials_string
142 135 diff = []
143 136 shellout(cmd) do |io|
144 137 io.each_line do |line|
145 138 diff << line
146 139 end
147 140 end
148 141 return nil if $? && $?.exitstatus != 0
149 142 DiffTableList.new diff, type
150 143 end
151 144
152 145 def cat(path, identifier=nil)
153 146 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
154 147 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
155 148 cmd << credentials_string
156 149 cat = nil
157 150 shellout(cmd) do |io|
158 151 io.binmode
159 152 cat = io.read
160 153 end
161 154 return nil if $? && $?.exitstatus != 0
162 155 cat
163 156 end
164 157
165 158 def annotate(path, identifier=nil)
166 159 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
167 160 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
168 161 cmd << credentials_string
169 162 blame = Annotate.new
170 163 shellout(cmd) do |io|
171 164 io.each_line do |line|
172 165 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
173 166 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
174 167 end
175 168 end
176 169 return nil if $? && $?.exitstatus != 0
177 170 blame
178 171 end
179 172
180 173 private
181 174
182 175 def credentials_string
183 176 str = ''
184 177 str << " --username #{shell_quote(@login)}" unless @login.blank?
185 178 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
186 179 str
187 180 end
188 181 end
189 182 end
190 183 end
191 184 end
@@ -1,129 +1,137
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 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 File.dirname(__FILE__) + '/../test_helper'
19 19 require 'repositories_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class RepositoriesController; def rescue_action(e) raise e end; end
23 23
24 24 class RepositoriesBazaarControllerTest < Test::Unit::TestCase
25 25 fixtures :projects, :users, :roles, :members, :repositories, :enabled_modules
26 26
27 27 # No '..' in the repository path
28 28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/bazaar_repository'
29 29
30 30 def setup
31 31 @controller = RepositoriesController.new
32 32 @request = ActionController::TestRequest.new
33 33 @response = ActionController::TestResponse.new
34 34 User.current = nil
35 35 Repository::Bazaar.create(:project => Project.find(3), :url => REPOSITORY_PATH)
36 36 end
37 37
38 38 if File.directory?(REPOSITORY_PATH)
39 39 def test_show
40 40 get :show, :id => 3
41 41 assert_response :success
42 42 assert_template 'show'
43 43 assert_not_nil assigns(:entries)
44 44 assert_not_nil assigns(:changesets)
45 45 end
46 46
47 47 def test_browse_root
48 48 get :browse, :id => 3
49 49 assert_response :success
50 50 assert_template 'browse'
51 51 assert_not_nil assigns(:entries)
52 52 assert_equal 2, assigns(:entries).size
53 53 assert assigns(:entries).detect {|e| e.name == 'directory' && e.kind == 'dir'}
54 54 assert assigns(:entries).detect {|e| e.name == 'doc-mkdir.txt' && e.kind == 'file'}
55 55 end
56 56
57 57 def test_browse_directory
58 58 get :browse, :id => 3, :path => ['directory']
59 59 assert_response :success
60 60 assert_template 'browse'
61 61 assert_not_nil assigns(:entries)
62 62 assert_equal ['doc-ls.txt', 'document.txt', 'edit.png'], assigns(:entries).collect(&:name)
63 63 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
64 64 assert_not_nil entry
65 65 assert_equal 'file', entry.kind
66 66 assert_equal 'directory/edit.png', entry.path
67 67 end
68 68
69 69 def test_browse_at_given_revision
70 70 get :browse, :id => 3, :path => [], :rev => 3
71 71 assert_response :success
72 72 assert_template 'browse'
73 73 assert_not_nil assigns(:entries)
74 74 assert_equal ['directory', 'doc-deleted.txt', 'doc-ls.txt', 'doc-mkdir.txt'], assigns(:entries).collect(&:name)
75 75 end
76 76
77 77 def test_changes
78 78 get :changes, :id => 3, :path => ['doc-mkdir.txt']
79 79 assert_response :success
80 80 assert_template 'changes'
81 81 assert_tag :tag => 'h2', :content => 'doc-mkdir.txt'
82 82 end
83 83
84 84 def test_entry_show
85 85 get :entry, :id => 3, :path => ['directory', 'doc-ls.txt']
86 86 assert_response :success
87 87 assert_template 'entry'
88 88 # Line 19
89 89 assert_tag :tag => 'th',
90 90 :content => /29/,
91 91 :attributes => { :class => /line-num/ },
92 92 :sibling => { :tag => 'td', :content => /Show help message/ }
93 93 end
94 94
95 95 def test_entry_download
96 96 get :entry, :id => 3, :path => ['directory', 'doc-ls.txt'], :format => 'raw'
97 97 assert_response :success
98 98 # File content
99 99 assert @response.body.include?('Show help message')
100 100 end
101 101
102 def test_directory_entry
103 get :entry, :id => 3, :path => ['directory']
104 assert_response :success
105 assert_template 'browse'
106 assert_not_nil assigns(:entry)
107 assert_equal 'directory', assigns(:entry).name
108 end
109
102 110 def test_diff
103 111 # Full diff of changeset 3
104 112 get :diff, :id => 3, :rev => 3
105 113 assert_response :success
106 114 assert_template 'diff'
107 115 # Line 22 removed
108 116 assert_tag :tag => 'th',
109 117 :content => /2/,
110 118 :sibling => { :tag => 'td',
111 119 :attributes => { :class => /diff_in/ },
112 120 :content => /Main purpose/ }
113 121 end
114 122
115 123 def test_annotate
116 124 get :annotate, :id => 3, :path => ['doc-mkdir.txt']
117 125 assert_response :success
118 126 assert_template 'annotate'
119 127 # Line 2, revision 3
120 128 assert_tag :tag => 'th', :content => /2/,
121 129 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /3/ } },
122 130 :sibling => { :tag => 'td', :content => /jsmith/ },
123 131 :sibling => { :tag => 'td', :content => /Main purpose/ }
124 132 end
125 133 else
126 134 puts "Bazaar test repository NOT FOUND. Skipping functional tests !!!"
127 135 def test_fake; assert true end
128 136 end
129 137 end
@@ -1,144 +1,152
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 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 File.dirname(__FILE__) + '/../test_helper'
19 19 require 'repositories_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class RepositoriesController; def rescue_action(e) raise e end; end
23 23
24 24 class RepositoriesCvsControllerTest < Test::Unit::TestCase
25 25
26 26 # No '..' in the repository path
27 27 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/cvs_repository'
28 28 REPOSITORY_PATH.gsub!(/\//, "\\") if RUBY_PLATFORM =~ /mswin/
29 29 # CVS module
30 30 MODULE_NAME = 'test'
31 31
32 32 def setup
33 33 @controller = RepositoriesController.new
34 34 @request = ActionController::TestRequest.new
35 35 @response = ActionController::TestResponse.new
36 36 Setting.default_language = 'en'
37 37 User.current = nil
38 38
39 39 @project = Project.find(1)
40 40 @project.repository = Repository::Cvs.create(:root_url => REPOSITORY_PATH,
41 41 :url => MODULE_NAME)
42 42 end
43 43
44 44 if File.directory?(REPOSITORY_PATH)
45 45 def test_show
46 46 get :show, :id => 1
47 47 assert_response :success
48 48 assert_template 'show'
49 49 assert_not_nil assigns(:entries)
50 50 assert_not_nil assigns(:changesets)
51 51 end
52 52
53 53 def test_browse_root
54 54 get :browse, :id => 1
55 55 assert_response :success
56 56 assert_template 'browse'
57 57 assert_not_nil assigns(:entries)
58 58 assert_equal 3, assigns(:entries).size
59 59
60 60 entry = assigns(:entries).detect {|e| e.name == 'images'}
61 61 assert_equal 'dir', entry.kind
62 62
63 63 entry = assigns(:entries).detect {|e| e.name == 'README'}
64 64 assert_equal 'file', entry.kind
65 65 end
66 66
67 67 def test_browse_directory
68 68 get :browse, :id => 1, :path => ['images']
69 69 assert_response :success
70 70 assert_template 'browse'
71 71 assert_not_nil assigns(:entries)
72 72 assert_equal ['add.png', 'delete.png', 'edit.png'], assigns(:entries).collect(&:name)
73 73 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
74 74 assert_not_nil entry
75 75 assert_equal 'file', entry.kind
76 76 assert_equal 'images/edit.png', entry.path
77 77 end
78 78
79 79 def test_browse_at_given_revision
80 80 Project.find(1).repository.fetch_changesets
81 81 get :browse, :id => 1, :path => ['images'], :rev => 1
82 82 assert_response :success
83 83 assert_template 'browse'
84 84 assert_not_nil assigns(:entries)
85 85 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
86 86 end
87 87
88 88 def test_entry
89 89 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb']
90 90 assert_response :success
91 91 assert_template 'entry'
92 92 end
93 93
94 94 def test_entry_not_found
95 95 get :entry, :id => 1, :path => ['sources', 'zzz.c']
96 96 assert_tag :tag => 'div', :attributes => { :class => /error/ },
97 97 :content => /The entry or revision was not found in the repository/
98 98 end
99 99
100 100 def test_entry_download
101 101 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
102 102 assert_response :success
103 103 end
104 104
105 def test_directory_entry
106 get :entry, :id => 1, :path => ['sources']
107 assert_response :success
108 assert_template 'browse'
109 assert_not_nil assigns(:entry)
110 assert_equal 'sources', assigns(:entry).name
111 end
112
105 113 def test_diff
106 114 Project.find(1).repository.fetch_changesets
107 115 get :diff, :id => 1, :rev => 3, :type => 'inline'
108 116 assert_response :success
109 117 assert_template 'diff'
110 118 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_out' },
111 119 :content => /watched.remove_watcher/
112 120 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' },
113 121 :content => /watched.remove_all_watcher/
114 122 end
115 123
116 124 def test_annotate
117 125 Project.find(1).repository.fetch_changesets
118 126 get :annotate, :id => 1, :path => ['sources', 'watchers_controller.rb']
119 127 assert_response :success
120 128 assert_template 'annotate'
121 129 # 1.1 line
122 130 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
123 131 :content => '18',
124 132 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
125 133 :content => /1.1/,
126 134 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
127 135 :content => /LANG/
128 136 }
129 137 }
130 138 # 1.2 line
131 139 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
132 140 :content => '32',
133 141 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
134 142 :content => /1.2/,
135 143 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
136 144 :content => /LANG/
137 145 }
138 146 }
139 147 end
140 148 else
141 149 puts "CVS test repository NOT FOUND. Skipping functional tests !!!"
142 150 def test_fake; assert true end
143 151 end
144 152 end
@@ -1,138 +1,146
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 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 File.dirname(__FILE__) + '/../test_helper'
19 19 require 'repositories_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class RepositoriesController; def rescue_action(e) raise e end; end
23 23
24 24 class RepositoriesGitControllerTest < Test::Unit::TestCase
25 25 fixtures :projects, :users, :roles, :members, :repositories, :enabled_modules
26 26
27 27 # No '..' in the repository path
28 28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
29 29 REPOSITORY_PATH.gsub!(/\//, "\\") if RUBY_PLATFORM =~ /mswin/
30 30
31 31 def setup
32 32 @controller = RepositoriesController.new
33 33 @request = ActionController::TestRequest.new
34 34 @response = ActionController::TestResponse.new
35 35 User.current = nil
36 36 Repository::Git.create(:project => Project.find(3), :url => REPOSITORY_PATH)
37 37 end
38 38
39 39 if File.directory?(REPOSITORY_PATH)
40 40 def test_show
41 41 get :show, :id => 3
42 42 assert_response :success
43 43 assert_template 'show'
44 44 assert_not_nil assigns(:entries)
45 45 assert_not_nil assigns(:changesets)
46 46 end
47 47
48 48 def test_browse_root
49 49 get :browse, :id => 3
50 50 assert_response :success
51 51 assert_template 'browse'
52 52 assert_not_nil assigns(:entries)
53 53 assert_equal 3, assigns(:entries).size
54 54 assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
55 55 assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
56 56 assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
57 57 end
58 58
59 59 def test_browse_directory
60 60 get :browse, :id => 3, :path => ['images']
61 61 assert_response :success
62 62 assert_template 'browse'
63 63 assert_not_nil assigns(:entries)
64 64 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
65 65 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
66 66 assert_not_nil entry
67 67 assert_equal 'file', entry.kind
68 68 assert_equal 'images/edit.png', entry.path
69 69 end
70 70
71 71 def test_browse_at_given_revision
72 72 get :browse, :id => 3, :path => ['images'], :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518'
73 73 assert_response :success
74 74 assert_template 'browse'
75 75 assert_not_nil assigns(:entries)
76 76 assert_equal ['delete.png'], assigns(:entries).collect(&:name)
77 77 end
78 78
79 79 def test_changes
80 80 get :changes, :id => 3, :path => ['images', 'edit.png']
81 81 assert_response :success
82 82 assert_template 'changes'
83 83 assert_tag :tag => 'h2', :content => 'edit.png'
84 84 end
85 85
86 86 def test_entry_show
87 87 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
88 88 assert_response :success
89 89 assert_template 'entry'
90 90 # Line 19
91 91 assert_tag :tag => 'th',
92 92 :content => /10/,
93 93 :attributes => { :class => /line-num/ },
94 94 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
95 95 end
96 96
97 97 def test_entry_download
98 98 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
99 99 assert_response :success
100 100 # File content
101 101 assert @response.body.include?('WITHOUT ANY WARRANTY')
102 102 end
103 103
104 def test_directory_entry
105 get :entry, :id => 3, :path => ['sources']
106 assert_response :success
107 assert_template 'browse'
108 assert_not_nil assigns(:entry)
109 assert_equal 'sources', assigns(:entry).name
110 end
111
104 112 def test_diff
105 113 # Full diff of changeset 2f9c0091
106 114 get :diff, :id => 3, :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
107 115 assert_response :success
108 116 assert_template 'diff'
109 117 # Line 22 removed
110 118 assert_tag :tag => 'th',
111 119 :content => /22/,
112 120 :sibling => { :tag => 'td',
113 121 :attributes => { :class => /diff_out/ },
114 122 :content => /def remove/ }
115 123 end
116 124
117 125 def test_annotate
118 126 get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
119 127 assert_response :success
120 128 assert_template 'annotate'
121 129 # Line 23, changeset 2f9c0091
122 130 assert_tag :tag => 'th', :content => /23/,
123 131 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /2f9c0091/ } },
124 132 :sibling => { :tag => 'td', :content => /jsmith/ },
125 133 :sibling => { :tag => 'td', :content => /watcher =/ }
126 134 end
127 135
128 136 def test_annotate_binary_file
129 137 get :annotate, :id => 3, :path => ['images', 'delete.png']
130 138 assert_response 500
131 139 assert_tag :tag => 'div', :attributes => { :class => /error/ },
132 140 :content => /can not be annotated/
133 141 end
134 142 else
135 143 puts "Git test repository NOT FOUND. Skipping functional tests !!!"
136 144 def test_fake; assert true end
137 145 end
138 146 end
@@ -1,130 +1,138
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 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 File.dirname(__FILE__) + '/../test_helper'
19 19 require 'repositories_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class RepositoriesController; def rescue_action(e) raise e end; end
23 23
24 24 class RepositoriesMercurialControllerTest < Test::Unit::TestCase
25 25 fixtures :projects, :users, :roles, :members, :repositories, :enabled_modules
26 26
27 27 # No '..' in the repository path
28 28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository'
29 29
30 30 def setup
31 31 @controller = RepositoriesController.new
32 32 @request = ActionController::TestRequest.new
33 33 @response = ActionController::TestResponse.new
34 34 User.current = nil
35 35 Repository::Mercurial.create(:project => Project.find(3), :url => REPOSITORY_PATH)
36 36 end
37 37
38 38 if File.directory?(REPOSITORY_PATH)
39 39 def test_show
40 40 get :show, :id => 3
41 41 assert_response :success
42 42 assert_template 'show'
43 43 assert_not_nil assigns(:entries)
44 44 assert_not_nil assigns(:changesets)
45 45 end
46 46
47 47 def test_browse_root
48 48 get :browse, :id => 3
49 49 assert_response :success
50 50 assert_template 'browse'
51 51 assert_not_nil assigns(:entries)
52 52 assert_equal 3, assigns(:entries).size
53 53 assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
54 54 assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
55 55 assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
56 56 end
57 57
58 58 def test_browse_directory
59 59 get :browse, :id => 3, :path => ['images']
60 60 assert_response :success
61 61 assert_template 'browse'
62 62 assert_not_nil assigns(:entries)
63 63 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
64 64 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
65 65 assert_not_nil entry
66 66 assert_equal 'file', entry.kind
67 67 assert_equal 'images/edit.png', entry.path
68 68 end
69 69
70 70 def test_browse_at_given_revision
71 71 get :browse, :id => 3, :path => ['images'], :rev => 0
72 72 assert_response :success
73 73 assert_template 'browse'
74 74 assert_not_nil assigns(:entries)
75 75 assert_equal ['delete.png'], assigns(:entries).collect(&:name)
76 76 end
77 77
78 78 def test_changes
79 79 get :changes, :id => 3, :path => ['images', 'edit.png']
80 80 assert_response :success
81 81 assert_template 'changes'
82 82 assert_tag :tag => 'h2', :content => 'edit.png'
83 83 end
84 84
85 85 def test_entry_show
86 86 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
87 87 assert_response :success
88 88 assert_template 'entry'
89 89 # Line 19
90 90 assert_tag :tag => 'th',
91 91 :content => /10/,
92 92 :attributes => { :class => /line-num/ },
93 93 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
94 94 end
95 95
96 96 def test_entry_download
97 97 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
98 98 assert_response :success
99 99 # File content
100 100 assert @response.body.include?('WITHOUT ANY WARRANTY')
101 101 end
102 102
103 def test_directory_entry
104 get :entry, :id => 3, :path => ['sources']
105 assert_response :success
106 assert_template 'browse'
107 assert_not_nil assigns(:entry)
108 assert_equal 'sources', assigns(:entry).name
109 end
110
103 111 def test_diff
104 112 # Full diff of changeset 4
105 113 get :diff, :id => 3, :rev => 4
106 114 assert_response :success
107 115 assert_template 'diff'
108 116 # Line 22 removed
109 117 assert_tag :tag => 'th',
110 118 :content => /22/,
111 119 :sibling => { :tag => 'td',
112 120 :attributes => { :class => /diff_out/ },
113 121 :content => /def remove/ }
114 122 end
115 123
116 124 def test_annotate
117 125 get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
118 126 assert_response :success
119 127 assert_template 'annotate'
120 128 # Line 23, revision 4
121 129 assert_tag :tag => 'th', :content => /23/,
122 130 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /4/ } },
123 131 :sibling => { :tag => 'td', :content => /jsmith/ },
124 132 :sibling => { :tag => 'td', :content => /watcher =/ }
125 133 end
126 134 else
127 135 puts "Mercurial test repository NOT FOUND. Skipping functional tests !!!"
128 136 def test_fake; assert true end
129 137 end
130 138 end
@@ -1,107 +1,115
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 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 File.dirname(__FILE__) + '/../test_helper'
19 19 require 'repositories_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class RepositoriesController; def rescue_action(e) raise e end; end
23 23
24 24 class RepositoriesSubversionControllerTest < Test::Unit::TestCase
25 25 fixtures :projects, :users, :roles, :members, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
26 26
27 27 # No '..' in the repository path for svn
28 28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/subversion_repository'
29 29
30 30 def setup
31 31 @controller = RepositoriesController.new
32 32 @request = ActionController::TestRequest.new
33 33 @response = ActionController::TestResponse.new
34 34 Setting.default_language = 'en'
35 35 User.current = nil
36 36 end
37 37
38 38 if File.directory?(REPOSITORY_PATH)
39 39 def test_show
40 40 get :show, :id => 1
41 41 assert_response :success
42 42 assert_template 'show'
43 43 assert_not_nil assigns(:entries)
44 44 assert_not_nil assigns(:changesets)
45 45 end
46 46
47 47 def test_browse_root
48 48 get :browse, :id => 1
49 49 assert_response :success
50 50 assert_template 'browse'
51 51 assert_not_nil assigns(:entries)
52 52 entry = assigns(:entries).detect {|e| e.name == 'subversion_test'}
53 53 assert_equal 'dir', entry.kind
54 54 end
55 55
56 56 def test_browse_directory
57 57 get :browse, :id => 1, :path => ['subversion_test']
58 58 assert_response :success
59 59 assert_template 'browse'
60 60 assert_not_nil assigns(:entries)
61 61 assert_equal ['folder', '.project', 'helloworld.c', 'textfile.txt'], assigns(:entries).collect(&:name)
62 62 entry = assigns(:entries).detect {|e| e.name == 'helloworld.c'}
63 63 assert_equal 'file', entry.kind
64 64 assert_equal 'subversion_test/helloworld.c', entry.path
65 65 end
66 66
67 67 def test_browse_at_given_revision
68 68 get :browse, :id => 1, :path => ['subversion_test'], :rev => 4
69 69 assert_response :success
70 70 assert_template 'browse'
71 71 assert_not_nil assigns(:entries)
72 72 assert_equal ['folder', '.project', 'helloworld.c', 'helloworld.rb', 'textfile.txt'], assigns(:entries).collect(&:name)
73 73 end
74 74
75 75 def test_entry
76 76 get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c']
77 77 assert_response :success
78 78 assert_template 'entry'
79 79 end
80 80
81 81 def test_entry_not_found
82 82 get :entry, :id => 1, :path => ['subversion_test', 'zzz.c']
83 83 assert_tag :tag => 'div', :attributes => { :class => /error/ },
84 84 :content => /The entry or revision was not found in the repository/
85 85 end
86 86
87 87 def test_entry_download
88 88 get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c'], :format => 'raw'
89 89 assert_response :success
90 90 end
91 91
92 def test_directory_entry
93 get :entry, :id => 1, :path => ['subversion_test', 'folder']
94 assert_response :success
95 assert_template 'browse'
96 assert_not_nil assigns(:entry)
97 assert_equal 'folder', assigns(:entry).name
98 end
99
92 100 def test_diff
93 101 get :diff, :id => 1, :rev => 3
94 102 assert_response :success
95 103 assert_template 'diff'
96 104 end
97 105
98 106 def test_annotate
99 107 get :annotate, :id => 1, :path => ['subversion_test', 'helloworld.c']
100 108 assert_response :success
101 109 assert_template 'annotate'
102 110 end
103 111 else
104 112 puts "Subversion test repository NOT FOUND. Skipping functional tests !!!"
105 113 def test_fake; assert true end
106 114 end
107 115 end
General Comments 0
You need to be logged in to leave comments. Login now