##// END OF EJS Templates
* Added links to previous and next revisions on revision view (patch by Cyril Mougel slightly edited)...
Jean-Philippe Lang -
r925:db002edabdec
parent child
Show More
@@ -0,0 +1,46
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'repositories_controller'
20
21 # Re-raise errors caught by the controller.
22 class RepositoriesController; def rescue_action(e) raise e end; end
23
24 class RepositoriesControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :members, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
26
27 def setup
28 @controller = RepositoriesController.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
32 end
33
34 def test_revision_with_before_nil_and_afer_normal
35 get :revision, {:id => 1, :rev => 1}
36 assert_response :success
37 assert_template 'revision'
38 assert_no_tag :tag => "div", :attributes => { :class => "contextual" },
39 :child => { :tag => "a", :attributes => { :href => '/repositories/revision/1?rev=0'}
40 }
41 assert_tag :tag => "div", :attributes => { :class => "contextual" },
42 :child => { :tag => "a", :attributes => { :href => '/repositories/revision/1?rev=2'}
43 }
44 end
45
46 end
@@ -1,264 +1,272
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'SVG/Graph/Bar'
18 require 'SVG/Graph/Bar'
19 require 'SVG/Graph/BarHorizontal'
19 require 'SVG/Graph/BarHorizontal'
20 require 'digest/sha1'
20 require 'digest/sha1'
21
21
22 class ChangesetNotFound < Exception
23 end
24
22 class RepositoriesController < ApplicationController
25 class RepositoriesController < ApplicationController
23 layout 'base'
26 layout 'base'
24 before_filter :find_repository, :except => :edit
27 before_filter :find_repository, :except => :edit
25 before_filter :find_project, :only => :edit
28 before_filter :find_project, :only => :edit
26 before_filter :authorize
29 before_filter :authorize
27 accept_key_auth :revisions
30 accept_key_auth :revisions
28
31
29 def edit
32 def edit
30 @repository = @project.repository
33 @repository = @project.repository
31 if !@repository
34 if !@repository
32 @repository = Repository.factory(params[:repository_scm])
35 @repository = Repository.factory(params[:repository_scm])
33 @repository.project = @project
36 @repository.project = @project
34 end
37 end
35 if request.post?
38 if request.post?
36 @repository.attributes = params[:repository]
39 @repository.attributes = params[:repository]
37 @repository.save
40 @repository.save
38 end
41 end
39 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
42 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
40 end
43 end
41
44
42 def destroy
45 def destroy
43 @repository.destroy
46 @repository.destroy
44 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
47 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
45 end
48 end
46
49
47 def show
50 def show
48 # check if new revisions have been committed in the repository
51 # check if new revisions have been committed in the repository
49 @repository.fetch_changesets if Setting.autofetch_changesets?
52 @repository.fetch_changesets if Setting.autofetch_changesets?
50 # get entries for the browse frame
53 # get entries for the browse frame
51 @entries = @repository.entries('')
54 @entries = @repository.entries('')
52 # latest changesets
55 # latest changesets
53 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
56 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
54 show_error and return unless @entries || @changesets.any?
57 show_error and return unless @entries || @changesets.any?
55 end
58 end
56
59
57 def browse
60 def browse
58 @entries = @repository.entries(@path, @rev)
61 @entries = @repository.entries(@path, @rev)
59 if request.xhr?
62 if request.xhr?
60 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
63 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
61 else
64 else
62 show_error unless @entries
65 show_error unless @entries
63 end
66 end
64 end
67 end
65
68
66 def changes
69 def changes
67 @entry = @repository.scm.entry(@path, @rev)
70 @entry = @repository.scm.entry(@path, @rev)
68 show_error and return unless @entry
71 show_error and return unless @entry
69 @changesets = @repository.changesets_for_path(@path)
72 @changesets = @repository.changesets_for_path(@path)
70 end
73 end
71
74
72 def revisions
75 def revisions
73 @changeset_count = @repository.changesets.count
76 @changeset_count = @repository.changesets.count
74 @changeset_pages = Paginator.new self, @changeset_count,
77 @changeset_pages = Paginator.new self, @changeset_count,
75 25,
78 25,
76 params['page']
79 params['page']
77 @changesets = @repository.changesets.find(:all,
80 @changesets = @repository.changesets.find(:all,
78 :limit => @changeset_pages.items_per_page,
81 :limit => @changeset_pages.items_per_page,
79 :offset => @changeset_pages.current.offset)
82 :offset => @changeset_pages.current.offset)
80
83
81 respond_to do |format|
84 respond_to do |format|
82 format.html { render :layout => false if request.xhr? }
85 format.html { render :layout => false if request.xhr? }
83 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
86 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
84 end
87 end
85 end
88 end
86
89
87 def entry
90 def entry
88 @content = @repository.scm.cat(@path, @rev)
91 @content = @repository.scm.cat(@path, @rev)
89 show_error and return unless @content
92 show_error and return unless @content
90 if 'raw' == params[:format]
93 if 'raw' == params[:format]
91 send_data @content, :filename => @path.split('/').last
94 send_data @content, :filename => @path.split('/').last
92 end
95 end
93 end
96 end
94
97
95 def revision
98 def revision
96 @changeset = @repository.changesets.find_by_revision(@rev)
99 @changeset = @repository.changesets.find_by_revision(@rev)
97 show_error and return unless @changeset
100 raise ChangesetNotFound unless @changeset
98 @changes_count = @changeset.changes.size
101 @changes_count = @changeset.changes.size
99 @changes_pages = Paginator.new self, @changes_count, 150, params['page']
102 @changes_pages = Paginator.new self, @changes_count, 150, params['page']
100 @changes = @changeset.changes.find(:all,
103 @changes = @changeset.changes.find(:all,
101 :limit => @changes_pages.items_per_page,
104 :limit => @changes_pages.items_per_page,
102 :offset => @changes_pages.current.offset)
105 :offset => @changes_pages.current.offset)
103
106
104 render :action => "revision", :layout => false if request.xhr?
107 respond_to do |format|
108 format.html
109 format.js {render :layout => false}
110 end
111 rescue ChangesetNotFound
112 show_error
105 end
113 end
106
114
107 def diff
115 def diff
108 @rev_to = params[:rev_to] ? params[:rev_to].to_i : (@rev - 1)
116 @rev_to = params[:rev_to] ? params[:rev_to].to_i : (@rev - 1)
109 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
117 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
110 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
118 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
111
119
112 # Save diff type as user preference
120 # Save diff type as user preference
113 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
121 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
114 User.current.pref[:diff_type] = @diff_type
122 User.current.pref[:diff_type] = @diff_type
115 User.current.preference.save
123 User.current.preference.save
116 end
124 end
117
125
118 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
126 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
119 unless read_fragment(@cache_key)
127 unless read_fragment(@cache_key)
120 @diff = @repository.diff(@path, @rev, @rev_to, @diff_type)
128 @diff = @repository.diff(@path, @rev, @rev_to, @diff_type)
121 show_error and return unless @diff
129 show_error and return unless @diff
122 end
130 end
123 end
131 end
124
132
125 def stats
133 def stats
126 end
134 end
127
135
128 def graph
136 def graph
129 data = nil
137 data = nil
130 case params[:graph]
138 case params[:graph]
131 when "commits_per_month"
139 when "commits_per_month"
132 data = graph_commits_per_month(@repository)
140 data = graph_commits_per_month(@repository)
133 when "commits_per_author"
141 when "commits_per_author"
134 data = graph_commits_per_author(@repository)
142 data = graph_commits_per_author(@repository)
135 end
143 end
136 if data
144 if data
137 headers["Content-Type"] = "image/svg+xml"
145 headers["Content-Type"] = "image/svg+xml"
138 send_data(data, :type => "image/svg+xml", :disposition => "inline")
146 send_data(data, :type => "image/svg+xml", :disposition => "inline")
139 else
147 else
140 render_404
148 render_404
141 end
149 end
142 end
150 end
143
151
144 private
152 private
145 def find_project
153 def find_project
146 @project = Project.find(params[:id])
154 @project = Project.find(params[:id])
147 rescue ActiveRecord::RecordNotFound
155 rescue ActiveRecord::RecordNotFound
148 render_404
156 render_404
149 end
157 end
150
158
151 def find_repository
159 def find_repository
152 @project = Project.find(params[:id])
160 @project = Project.find(params[:id])
153 @repository = @project.repository
161 @repository = @project.repository
154 render_404 and return false unless @repository
162 render_404 and return false unless @repository
155 @path = params[:path].join('/') unless params[:path].nil?
163 @path = params[:path].join('/') unless params[:path].nil?
156 @path ||= ''
164 @path ||= ''
157 @rev = params[:rev].to_i if params[:rev]
165 @rev = params[:rev].to_i if params[:rev]
158 rescue ActiveRecord::RecordNotFound
166 rescue ActiveRecord::RecordNotFound
159 render_404
167 render_404
160 end
168 end
161
169
162 def show_error
170 def show_error
163 flash.now[:error] = l(:notice_scm_error)
171 flash.now[:error] = l(:notice_scm_error)
164 render :nothing => true, :layout => true
172 render :nothing => true, :layout => true
165 end
173 end
166
174
167 def graph_commits_per_month(repository)
175 def graph_commits_per_month(repository)
168 @date_to = Date.today
176 @date_to = Date.today
169 @date_from = @date_to << 11
177 @date_from = @date_to << 11
170 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
178 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
171 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
179 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
172 commits_by_month = [0] * 12
180 commits_by_month = [0] * 12
173 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
181 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
174
182
175 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
183 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
176 changes_by_month = [0] * 12
184 changes_by_month = [0] * 12
177 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
185 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
178
186
179 fields = []
187 fields = []
180 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
188 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
181 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
189 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
182
190
183 graph = SVG::Graph::Bar.new(
191 graph = SVG::Graph::Bar.new(
184 :height => 300,
192 :height => 300,
185 :width => 500,
193 :width => 500,
186 :fields => fields.reverse,
194 :fields => fields.reverse,
187 :stack => :side,
195 :stack => :side,
188 :scale_integers => true,
196 :scale_integers => true,
189 :step_x_labels => 2,
197 :step_x_labels => 2,
190 :show_data_values => false,
198 :show_data_values => false,
191 :graph_title => l(:label_commits_per_month),
199 :graph_title => l(:label_commits_per_month),
192 :show_graph_title => true
200 :show_graph_title => true
193 )
201 )
194
202
195 graph.add_data(
203 graph.add_data(
196 :data => commits_by_month[0..11].reverse,
204 :data => commits_by_month[0..11].reverse,
197 :title => l(:label_revision_plural)
205 :title => l(:label_revision_plural)
198 )
206 )
199
207
200 graph.add_data(
208 graph.add_data(
201 :data => changes_by_month[0..11].reverse,
209 :data => changes_by_month[0..11].reverse,
202 :title => l(:label_change_plural)
210 :title => l(:label_change_plural)
203 )
211 )
204
212
205 graph.burn
213 graph.burn
206 end
214 end
207
215
208 def graph_commits_per_author(repository)
216 def graph_commits_per_author(repository)
209 commits_by_author = repository.changesets.count(:all, :group => :committer)
217 commits_by_author = repository.changesets.count(:all, :group => :committer)
210 commits_by_author.sort! {|x, y| x.last <=> y.last}
218 commits_by_author.sort! {|x, y| x.last <=> y.last}
211
219
212 changes_by_author = repository.changes.count(:all, :group => :committer)
220 changes_by_author = repository.changes.count(:all, :group => :committer)
213 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
221 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
214
222
215 fields = commits_by_author.collect {|r| r.first}
223 fields = commits_by_author.collect {|r| r.first}
216 commits_data = commits_by_author.collect {|r| r.last}
224 commits_data = commits_by_author.collect {|r| r.last}
217 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
225 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
218
226
219 fields = fields + [""]*(10 - fields.length) if fields.length<10
227 fields = fields + [""]*(10 - fields.length) if fields.length<10
220 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
228 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
221 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
229 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
222
230
223 graph = SVG::Graph::BarHorizontal.new(
231 graph = SVG::Graph::BarHorizontal.new(
224 :height => 300,
232 :height => 300,
225 :width => 500,
233 :width => 500,
226 :fields => fields,
234 :fields => fields,
227 :stack => :side,
235 :stack => :side,
228 :scale_integers => true,
236 :scale_integers => true,
229 :show_data_values => false,
237 :show_data_values => false,
230 :rotate_y_labels => false,
238 :rotate_y_labels => false,
231 :graph_title => l(:label_commits_per_author),
239 :graph_title => l(:label_commits_per_author),
232 :show_graph_title => true
240 :show_graph_title => true
233 )
241 )
234
242
235 graph.add_data(
243 graph.add_data(
236 :data => commits_data,
244 :data => commits_data,
237 :title => l(:label_revision_plural)
245 :title => l(:label_revision_plural)
238 )
246 )
239
247
240 graph.add_data(
248 graph.add_data(
241 :data => changes_data,
249 :data => changes_data,
242 :title => l(:label_change_plural)
250 :title => l(:label_change_plural)
243 )
251 )
244
252
245 graph.burn
253 graph.burn
246 end
254 end
247
255
248 end
256 end
249
257
250 class Date
258 class Date
251 def months_ago(date = Date.today)
259 def months_ago(date = Date.today)
252 (date.year - self.year)*12 + (date.month - self.month)
260 (date.year - self.year)*12 + (date.month - self.month)
253 end
261 end
254
262
255 def weeks_ago(date = Date.today)
263 def weeks_ago(date = Date.today)
256 (date.year - self.year)*52 + (date.cweek - self.cweek)
264 (date.year - self.year)*52 + (date.cweek - self.cweek)
257 end
265 end
258 end
266 end
259
267
260 class String
268 class String
261 def with_leading_slash
269 def with_leading_slash
262 starts_with?('/') ? self : "/#{self}"
270 starts_with?('/') ? self : "/#{self}"
263 end
271 end
264 end
272 end
@@ -1,172 +1,172
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 layout 'base'
19 layout 'base'
20 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize
21
21
22 helper :sort
22 helper :sort
23 include SortHelper
23 include SortHelper
24 helper :issues
24 helper :issues
25
25
26 def report
26 def report
27 @available_criterias = { 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
27 @available_criterias = { 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
28 :values => @project.versions,
28 :values => @project.versions,
29 :label => :label_version},
29 :label => :label_version},
30 'category' => {:sql => "#{Issue.table_name}.category_id",
30 'category' => {:sql => "#{Issue.table_name}.category_id",
31 :values => @project.issue_categories,
31 :values => @project.issue_categories,
32 :label => :field_category},
32 :label => :field_category},
33 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
33 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
34 :values => @project.users,
34 :values => @project.users,
35 :label => :label_member},
35 :label => :label_member},
36 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
36 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
37 :values => Tracker.find(:all),
37 :values => Tracker.find(:all),
38 :label => :label_tracker},
38 :label => :label_tracker},
39 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
39 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
40 :values => Enumeration::get_values('ACTI'),
40 :values => Enumeration::get_values('ACTI'),
41 :label => :label_activity}
41 :label => :label_activity}
42 }
42 }
43
43
44 @criterias = params[:criterias] || []
44 @criterias = params[:criterias] || []
45 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
45 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
46 @criterias.uniq!
46 @criterias.uniq!
47
47
48 @columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month'
48 @columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month'
49
49
50 if params[:date_from]
50 if params[:date_from]
51 begin; @date_from = params[:date_from].to_date; rescue; end
51 begin; @date_from = params[:date_from].to_date; rescue; end
52 end
52 end
53 if params[:date_to]
53 if params[:date_to]
54 begin; @date_to = params[:date_to].to_date; rescue; end
54 begin; @date_to = params[:date_to].to_date; rescue; end
55 end
55 end
56 @date_from ||= Date.civil(Date.today.year, 1, 1)
56 @date_from ||= Date.civil(Date.today.year, 1, 1)
57 @date_to ||= Date.civil(Date.today.year, Date.today.month+1, 1) - 1
57 @date_to ||= (Date.civil(Date.today.year, Date.today.month, 1) >> 1) - 1
58
58
59 unless @criterias.empty?
59 unless @criterias.empty?
60 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
60 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
61 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
61 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
62
62
63 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
63 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
64 sql << " FROM #{TimeEntry.table_name} LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
64 sql << " FROM #{TimeEntry.table_name} LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
65 sql << " WHERE #{TimeEntry.table_name}.project_id = %s" % @project.id
65 sql << " WHERE #{TimeEntry.table_name}.project_id = %s" % @project.id
66 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)]
66 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)]
67 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
67 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
68
68
69 @hours = ActiveRecord::Base.connection.select_all(sql)
69 @hours = ActiveRecord::Base.connection.select_all(sql)
70
70
71 @hours.each do |row|
71 @hours.each do |row|
72 case @columns
72 case @columns
73 when 'year'
73 when 'year'
74 row['year'] = row['tyear']
74 row['year'] = row['tyear']
75 when 'month'
75 when 'month'
76 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
76 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
77 when 'week'
77 when 'week'
78 row['week'] = "#{row['tyear']}-#{row['tweek']}"
78 row['week'] = "#{row['tyear']}-#{row['tweek']}"
79 end
79 end
80 end
80 end
81 end
81 end
82
82
83 @periods = []
83 @periods = []
84 date_from = @date_from
84 date_from = @date_from
85 # 100 columns max
85 # 100 columns max
86 while date_from < @date_to && @periods.length < 100
86 while date_from < @date_to && @periods.length < 100
87 case @columns
87 case @columns
88 when 'year'
88 when 'year'
89 @periods << "#{date_from.year}"
89 @periods << "#{date_from.year}"
90 date_from = date_from >> 12
90 date_from = date_from >> 12
91 when 'month'
91 when 'month'
92 @periods << "#{date_from.year}-#{date_from.month}"
92 @periods << "#{date_from.year}-#{date_from.month}"
93 date_from = date_from >> 1
93 date_from = date_from >> 1
94 when 'week'
94 when 'week'
95 @periods << "#{date_from.year}-#{date_from.cweek}"
95 @periods << "#{date_from.year}-#{date_from.cweek}"
96 date_from = date_from + 7
96 date_from = date_from + 7
97 end
97 end
98 end
98 end
99
99
100 render :layout => false if request.xhr?
100 render :layout => false if request.xhr?
101 end
101 end
102
102
103 def details
103 def details
104 sort_init 'spent_on', 'desc'
104 sort_init 'spent_on', 'desc'
105 sort_update
105 sort_update
106
106
107 @entries = (@issue ? @issue : @project).time_entries.find(:all, :include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :order => sort_clause)
107 @entries = (@issue ? @issue : @project).time_entries.find(:all, :include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :order => sort_clause)
108
108
109 @total_hours = @entries.inject(0) { |sum,entry| sum + entry.hours }
109 @total_hours = @entries.inject(0) { |sum,entry| sum + entry.hours }
110 @owner_id = User.current.id
110 @owner_id = User.current.id
111
111
112 send_csv and return if 'csv' == params[:export]
112 send_csv and return if 'csv' == params[:export]
113 render :action => 'details', :layout => false if request.xhr?
113 render :action => 'details', :layout => false if request.xhr?
114 end
114 end
115
115
116 def edit
116 def edit
117 render_404 and return if @time_entry && @time_entry.user != User.current
117 render_404 and return if @time_entry && @time_entry.user != User.current
118 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
118 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
119 @time_entry.attributes = params[:time_entry]
119 @time_entry.attributes = params[:time_entry]
120 if request.post? and @time_entry.save
120 if request.post? and @time_entry.save
121 flash[:notice] = l(:notice_successful_update)
121 flash[:notice] = l(:notice_successful_update)
122 redirect_to :action => 'details', :project_id => @time_entry.project, :issue_id => @time_entry.issue
122 redirect_to :action => 'details', :project_id => @time_entry.project, :issue_id => @time_entry.issue
123 return
123 return
124 end
124 end
125 @activities = Enumeration::get_values('ACTI')
125 @activities = Enumeration::get_values('ACTI')
126 end
126 end
127
127
128 private
128 private
129 def find_project
129 def find_project
130 if params[:id]
130 if params[:id]
131 @time_entry = TimeEntry.find(params[:id])
131 @time_entry = TimeEntry.find(params[:id])
132 @project = @time_entry.project
132 @project = @time_entry.project
133 elsif params[:issue_id]
133 elsif params[:issue_id]
134 @issue = Issue.find(params[:issue_id])
134 @issue = Issue.find(params[:issue_id])
135 @project = @issue.project
135 @project = @issue.project
136 elsif params[:project_id]
136 elsif params[:project_id]
137 @project = Project.find(params[:project_id])
137 @project = Project.find(params[:project_id])
138 else
138 else
139 render_404
139 render_404
140 return false
140 return false
141 end
141 end
142 end
142 end
143
143
144 def send_csv
144 def send_csv
145 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
145 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
146 export = StringIO.new
146 export = StringIO.new
147 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
147 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
148 # csv header fields
148 # csv header fields
149 headers = [l(:field_spent_on),
149 headers = [l(:field_spent_on),
150 l(:field_user),
150 l(:field_user),
151 l(:field_activity),
151 l(:field_activity),
152 l(:field_issue),
152 l(:field_issue),
153 l(:field_hours),
153 l(:field_hours),
154 l(:field_comments)
154 l(:field_comments)
155 ]
155 ]
156 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
156 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
157 # csv lines
157 # csv lines
158 @entries.each do |entry|
158 @entries.each do |entry|
159 fields = [l_date(entry.spent_on),
159 fields = [l_date(entry.spent_on),
160 entry.user.name,
160 entry.user.name,
161 entry.activity.name,
161 entry.activity.name,
162 (entry.issue ? entry.issue.id : nil),
162 (entry.issue ? entry.issue.id : nil),
163 entry.hours,
163 entry.hours,
164 entry.comments
164 entry.comments
165 ]
165 ]
166 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
166 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
167 end
167 end
168 end
168 end
169 export.rewind
169 export.rewind
170 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
170 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
171 end
171 end
172 end
172 end
@@ -1,94 +1,104
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Changeset < ActiveRecord::Base
18 class Changeset < ActiveRecord::Base
19 belongs_to :repository
19 belongs_to :repository
20 has_many :changes, :dependent => :delete_all
20 has_many :changes, :dependent => :delete_all
21 has_and_belongs_to_many :issues
21 has_and_belongs_to_many :issues
22
22
23 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.comments.blank? ? '' : (': ' + o.comments))},
23 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.comments.blank? ? '' : (': ' + o.comments))},
24 :description => :comments,
24 :description => :comments,
25 :datetime => :committed_on,
25 :datetime => :committed_on,
26 :author => :committer,
26 :author => :committer,
27 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
27 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
28
28
29 acts_as_searchable :columns => 'comments',
29 acts_as_searchable :columns => 'comments',
30 :include => :repository,
30 :include => :repository,
31 :project_key => "#{Repository.table_name}.project_id",
31 :project_key => "#{Repository.table_name}.project_id",
32 :date_column => 'committed_on'
32 :date_column => 'committed_on'
33
33
34 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
34 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
35 validates_numericality_of :revision, :only_integer => true
35 validates_numericality_of :revision, :only_integer => true
36 validates_uniqueness_of :revision, :scope => :repository_id
36 validates_uniqueness_of :revision, :scope => :repository_id
37 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
37 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
38
38
39 def comments=(comment)
39 def comments=(comment)
40 write_attribute(:comments, comment.strip)
40 write_attribute(:comments, comment.strip)
41 end
41 end
42
42
43 def committed_on=(date)
43 def committed_on=(date)
44 self.commit_date = date
44 self.commit_date = date
45 super
45 super
46 end
46 end
47
47
48 def after_create
48 def after_create
49 scan_comment_for_issue_ids
49 scan_comment_for_issue_ids
50 end
50 end
51
51
52 def scan_comment_for_issue_ids
52 def scan_comment_for_issue_ids
53 return if comments.blank?
53 return if comments.blank?
54 # keywords used to reference issues
54 # keywords used to reference issues
55 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
55 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
56 # keywords used to fix issues
56 # keywords used to fix issues
57 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
57 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
58 # status and optional done ratio applied
58 # status and optional done ratio applied
59 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
59 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
60 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
60 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
61
61
62 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
62 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
63 return if kw_regexp.blank?
63 return if kw_regexp.blank?
64
64
65 referenced_issues = []
65 referenced_issues = []
66
66
67 if ref_keywords.delete('*')
67 if ref_keywords.delete('*')
68 # find any issue ID in the comments
68 # find any issue ID in the comments
69 target_issue_ids = []
69 target_issue_ids = []
70 comments.scan(%r{([\s\(,-^])#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
70 comments.scan(%r{([\s\(,-^])#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
71 referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
71 referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
72 end
72 end
73
73
74 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
74 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
75 action = match[0]
75 action = match[0]
76 target_issue_ids = match[1].scan(/\d+/)
76 target_issue_ids = match[1].scan(/\d+/)
77 target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
77 target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
78 if fix_status && fix_keywords.include?(action.downcase)
78 if fix_status && fix_keywords.include?(action.downcase)
79 # update status of issues
79 # update status of issues
80 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
80 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
81 target_issues.each do |issue|
81 target_issues.each do |issue|
82 # don't change the status is the issue is already closed
82 # don't change the status is the issue is already closed
83 next if issue.status.is_closed?
83 next if issue.status.is_closed?
84 issue.status = fix_status
84 issue.status = fix_status
85 issue.done_ratio = done_ratio if done_ratio
85 issue.done_ratio = done_ratio if done_ratio
86 issue.save
86 issue.save
87 end
87 end
88 end
88 end
89 referenced_issues += target_issues
89 referenced_issues += target_issues
90 end
90 end
91
91
92 self.issues = referenced_issues.uniq
92 self.issues = referenced_issues.uniq
93 end
93 end
94
95 # Returns the previous changeset
96 def previous
97 @previous ||= Changeset.find(:first, :conditions => ['revision < ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision DESC')
98 end
99
100 # Returns the next changeset
101 def next
102 @next ||= Changeset.find(:first, :conditions => ['revision > ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision ASC')
103 end
94 end
104 end
@@ -1,50 +1,64
1 <div class="contextual">
1 <div class="contextual">
2 <% form_tag do %>
2 &#171;
3 <%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %>
3 <% unless @changeset.previous.nil? -%>
4 <%= submit_tag 'OK' %>
4 <%= link_to l(:label_previous), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.previous.revision %>
5 <% end %>
5 <% else -%>
6 <%= l(:label_previous) %>
7 <% end -%>
8 |
9 <% unless @changeset.next.nil? -%>
10 <%= link_to l(:label_next), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.next.revision %>
11 <% else -%>
12 <%= l(:label_next) %>
13 <% end -%>
14 &#187;&nbsp;
15
16 <% form_tag do %>
17 <%= text_field_tag 'rev', @rev, :size => 5 %>
18 <%= submit_tag 'OK' %>
19 <% end %>
6 </div>
20 </div>
7
21
8 <h2><%= l(:label_revision) %> <%= @changeset.revision %></h2>
22 <h2><%= l(:label_revision) %> <%= @changeset.revision %></h2>
9
23
10 <p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
24 <p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
11 <em><%= @changeset.committer %>, <%= format_time(@changeset.committed_on) %></em></p>
25 <em><%= @changeset.committer %>, <%= format_time(@changeset.committed_on) %></em></p>
12
26
13 <%= textilizable @changeset.comments %>
27 <%= textilizable @changeset.comments %>
14
28
15 <% if @changeset.issues.any? %>
29 <% if @changeset.issues.any? %>
16 <h3><%= l(:label_related_issues) %></h3>
30 <h3><%= l(:label_related_issues) %></h3>
17 <ul>
31 <ul>
18 <% @changeset.issues.each do |issue| %>
32 <% @changeset.issues.each do |issue| %>
19 <li><%= link_to_issue issue %>: <%=h issue.subject %></li>
33 <li><%= link_to_issue issue %>: <%=h issue.subject %></li>
20 <% end %>
34 <% end %>
21 </ul>
35 </ul>
22 <% end %>
36 <% end %>
23
37
24 <h3><%= l(:label_attachment_plural) %></h3>
38 <h3><%= l(:label_attachment_plural) %></h3>
25 <div style="float:right;">
39 <div style="float:right;">
26 <div class="square action_A"></div> <div style="float:left;"><%= l(:label_added) %>&nbsp;</div>
40 <div class="square action_A"></div> <div style="float:left;"><%= l(:label_added) %>&nbsp;</div>
27 <div class="square action_M"></div> <div style="float:left;"><%= l(:label_modified) %>&nbsp;</div>
41 <div class="square action_M"></div> <div style="float:left;"><%= l(:label_modified) %>&nbsp;</div>
28 <div class="square action_D"></div> <div style="float:left;"><%= l(:label_deleted) %>&nbsp;</div>
42 <div class="square action_D"></div> <div style="float:left;"><%= l(:label_deleted) %>&nbsp;</div>
29 </div>
43 </div>
30 <p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.revision) if @changeset.changes.any? %></p>
44 <p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.revision) if @changeset.changes.any? %></p>
31 <table class="list">
45 <table class="list">
32 <tbody>
46 <tbody>
33 <% @changes.each do |change| %>
47 <% @changes.each do |change| %>
34 <tr class="<%= cycle 'odd', 'even' %>">
48 <tr class="<%= cycle 'odd', 'even' %>">
35 <td><div class="square action_<%= change.action %>"></div> <%= change.path %> <%= "(#{change.revision})" unless change.revision.blank? %></td>
49 <td><div class="square action_<%= change.action %>"></div> <%= change.path %> <%= "(#{change.revision})" unless change.revision.blank? %></td>
36 <td align="right">
50 <td align="right">
37 <% if change.action == "M" %>
51 <% if change.action == "M" %>
38 <%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => change.path, :rev => @changeset.revision %>
52 <%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => change.path, :rev => @changeset.revision %>
39 <% end %>
53 <% end %>
40 </td>
54 </td>
41 </tr>
55 </tr>
42 <% end %>
56 <% end %>
43 </tbody>
57 </tbody>
44 </table>
58 </table>
45 <p><%= pagination_links_full @changes_pages, :rev => @changeset.revision %>
59 <p><%= pagination_links_full @changes_pages, :rev => @changeset.revision %>
46 [ <%= @changes_pages.current.first_item %> - <%= @changes_pages.current.last_item %> / <%= @changes_count %> ]</p>
60 [ <%= @changes_pages.current.first_item %> - <%= @changes_pages.current.last_item %> / <%= @changes_count %> ]</p>
47
61
48 <% content_for :header_tags do %>
62 <% content_for :header_tags do %>
49 <%= stylesheet_link_tag "scm" %>
63 <%= stylesheet_link_tag "scm" %>
50 <% end %>
64 <% end %>
@@ -1,487 +1,489
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2
2
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 h1 {margin:0; padding:0; font-size: 24px;}
4 h1 {margin:0; padding:0; font-size: 24px;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 12px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 12px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8
8
9 /***** Layout *****/
9 /***** Layout *****/
10 #top-menu {background: #2C4056;color: #fff;height:1.5em; padding: 2px 6px 0px 6px;}
10 #top-menu {background: #2C4056;color: #fff;height:1.5em; padding: 2px 6px 0px 6px;}
11 #top-menu a {color: #fff; padding-right: 4px;}
11 #top-menu a {color: #fff; padding-right: 4px;}
12 #account {float:right;}
12 #account {float:right;}
13
13
14 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
14 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
15 #header a {color:#f8f8f8;}
15 #header a {color:#f8f8f8;}
16 #quick-search {float:right;}
16 #quick-search {float:right;}
17
17
18 #main-menu {position: absolute; bottom: 0px; left:6px;}
18 #main-menu {position: absolute; bottom: 0px; left:6px;}
19 #main-menu ul {margin: 0; padding: 0;}
19 #main-menu ul {margin: 0; padding: 0;}
20 #main-menu li {
20 #main-menu li {
21 float:left;
21 float:left;
22 list-style-type:none;
22 list-style-type:none;
23 margin: 0px 10px 0px 0px;
23 margin: 0px 10px 0px 0px;
24 padding: 0px 0px 0px 0px;
24 padding: 0px 0px 0px 0px;
25 white-space:nowrap;
25 white-space:nowrap;
26 }
26 }
27 #main-menu li a {
27 #main-menu li a {
28 display: block;
28 display: block;
29 color: #fff;
29 color: #fff;
30 text-decoration: none;
30 text-decoration: none;
31 margin: 0;
31 margin: 0;
32 padding: 4px 4px 4px 4px;
32 padding: 4px 4px 4px 4px;
33 background: #2C4056;
33 background: #2C4056;
34 }
34 }
35 #main-menu li a:hover {background:#759FCF;}
35 #main-menu li a:hover {background:#759FCF;}
36
36
37 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
37 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
38
38
39 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
39 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
40 * html #sidebar{ width: 17%; }
40 * html #sidebar{ width: 17%; }
41 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
41 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
42 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
42 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
43 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
43 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
44
44
45 #content { width: 80%; background: url(../images/contentbg.png) repeat-x; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
45 #content { width: 80%; background: url(../images/contentbg.png) repeat-x; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
46 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
46 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
47 html>body #content {
47 html>body #content {
48 height: auto;
48 height: auto;
49 min-height: 600px;
49 min-height: 600px;
50 }
50 }
51
51
52 #main.nosidebar #sidebar{ display: none; }
52 #main.nosidebar #sidebar{ display: none; }
53 #main.nosidebar #content{ width: auto; border-right: 0; }
53 #main.nosidebar #content{ width: auto; border-right: 0; }
54
54
55 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
55 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
56
56
57 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
57 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
58 #login-form table td {padding: 6px;}
58 #login-form table td {padding: 6px;}
59 #login-form label {font-weight: bold;}
59 #login-form label {font-weight: bold;}
60
60
61 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
61 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
62
62
63 /***** Links *****/
63 /***** Links *****/
64 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
64 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
65 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
65 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
66 a img{ border: 0; }
66 a img{ border: 0; }
67
67
68 /***** Tables *****/
68 /***** Tables *****/
69 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
69 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
70 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
70 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
71 table.list td { overflow: hidden; text-overflow: ellipsis; vertical-align: top;}
71 table.list td { overflow: hidden; text-overflow: ellipsis; vertical-align: top;}
72 table.list td.id { width: 2%; text-align: center;}
72 table.list td.id { width: 2%; text-align: center;}
73 table.list td.checkbox { width: 15px; padding: 0px;}
73 table.list td.checkbox { width: 15px; padding: 0px;}
74
74
75 tr.issue { text-align: center; white-space: nowrap; }
75 tr.issue { text-align: center; white-space: nowrap; }
76 tr.issue td.subject, tr.issue td.category { white-space: normal; }
76 tr.issue td.subject, tr.issue td.category { white-space: normal; }
77 tr.issue td.subject { text-align: left; }
77 tr.issue td.subject { text-align: left; }
78 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
78 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
79
79
80 tr.message { height: 2.6em; }
80 tr.message { height: 2.6em; }
81 tr.message td.last_message { font-size: 80%; }
81 tr.message td.last_message { font-size: 80%; }
82 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
82 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
83 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
83 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
84
84
85 table.list tbody tr:hover { background-color:#ffffdd; }
85 table.list tbody tr:hover { background-color:#ffffdd; }
86 table td {padding:2px;}
86 table td {padding:2px;}
87 table p {margin:0;}
87 table p {margin:0;}
88 .odd {background-color:#f6f7f8;}
88 .odd {background-color:#f6f7f8;}
89 .even {background-color: #fff;}
89 .even {background-color: #fff;}
90
90
91 .highlight { background-color: #FCFD8D;}
91 .highlight { background-color: #FCFD8D;}
92 .highlight.token-1 { background-color: #faa;}
92 .highlight.token-1 { background-color: #faa;}
93 .highlight.token-2 { background-color: #afa;}
93 .highlight.token-2 { background-color: #afa;}
94 .highlight.token-3 { background-color: #aaf;}
94 .highlight.token-3 { background-color: #aaf;}
95
95
96 .box{
96 .box{
97 padding:6px;
97 padding:6px;
98 margin-bottom: 10px;
98 margin-bottom: 10px;
99 background-color:#f6f6f6;
99 background-color:#f6f6f6;
100 color:#505050;
100 color:#505050;
101 line-height:1.5em;
101 line-height:1.5em;
102 border: 1px solid #e4e4e4;
102 border: 1px solid #e4e4e4;
103 }
103 }
104
104
105 div.square {
105 div.square {
106 border: 1px solid #999;
106 border: 1px solid #999;
107 float: left;
107 float: left;
108 margin: .3em .4em 0 .4em;
108 margin: .3em .4em 0 .4em;
109 overflow: hidden;
109 overflow: hidden;
110 width: .6em; height: .6em;
110 width: .6em; height: .6em;
111 }
111 }
112
112
113 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px;font-size:0.9em;}
113 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px;font-size:0.9em;}
114 .contextual input {font-size:0.9em;}
115
114 .splitcontentleft{float:left; width:49%;}
116 .splitcontentleft{float:left; width:49%;}
115 .splitcontentright{float:right; width:49%;}
117 .splitcontentright{float:right; width:49%;}
116 form {display: inline;}
118 form {display: inline;}
117 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
119 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
118 fieldset {border: 1px solid #e4e4e4; margin:0;}
120 fieldset {border: 1px solid #e4e4e4; margin:0;}
119 legend {color: #484848;}
121 legend {color: #484848;}
120 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
122 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
121 textarea.wiki-edit { width: 99%; }
123 textarea.wiki-edit { width: 99%; }
122 li p {margin-top: 0;}
124 li p {margin-top: 0;}
123 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
125 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
124 .autoscroll {overflow-x: auto; padding:1px; width:100%;}
126 .autoscroll {overflow-x: auto; padding:1px; width:100%;}
125 #user_firstname, #user_lastname, #user_mail, #notification_option { width: 90%; }
127 #user_firstname, #user_lastname, #user_mail, #notification_option { width: 90%; }
126
128
127 /***** Tabular forms ******/
129 /***** Tabular forms ******/
128 .tabular p{
130 .tabular p{
129 margin: 0;
131 margin: 0;
130 padding: 5px 0 8px 0;
132 padding: 5px 0 8px 0;
131 padding-left: 180px; /*width of left column containing the label elements*/
133 padding-left: 180px; /*width of left column containing the label elements*/
132 height: 1%;
134 height: 1%;
133 clear:left;
135 clear:left;
134 }
136 }
135
137
136 .tabular label{
138 .tabular label{
137 font-weight: bold;
139 font-weight: bold;
138 float: left;
140 float: left;
139 text-align: right;
141 text-align: right;
140 margin-left: -180px; /*width of left column*/
142 margin-left: -180px; /*width of left column*/
141 width: 175px; /*width of labels. Should be smaller than left column to create some right
143 width: 175px; /*width of labels. Should be smaller than left column to create some right
142 margin*/
144 margin*/
143 }
145 }
144
146
145 .tabular label.floating{
147 .tabular label.floating{
146 font-weight: normal;
148 font-weight: normal;
147 margin-left: 0px;
149 margin-left: 0px;
148 text-align: left;
150 text-align: left;
149 width: 200px;
151 width: 200px;
150 }
152 }
151
153
152 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
154 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
153
155
154 #settings .tabular p{ padding-left: 300px; }
156 #settings .tabular p{ padding-left: 300px; }
155 #settings .tabular label{ margin-left: -300px; width: 295px; }
157 #settings .tabular label{ margin-left: -300px; width: 295px; }
156
158
157 .required {color: #bb0000;}
159 .required {color: #bb0000;}
158 .summary {font-style: italic;}
160 .summary {font-style: italic;}
159
161
160 div.attachments p { margin:4px 0 2px 0; }
162 div.attachments p { margin:4px 0 2px 0; }
161
163
162 /***** Flash & error messages ****/
164 /***** Flash & error messages ****/
163 #errorExplanation, div.flash, .nodata {
165 #errorExplanation, div.flash, .nodata {
164 padding: 4px 4px 4px 30px;
166 padding: 4px 4px 4px 30px;
165 margin-bottom: 12px;
167 margin-bottom: 12px;
166 font-size: 1.1em;
168 font-size: 1.1em;
167 border: 2px solid;
169 border: 2px solid;
168 }
170 }
169
171
170 div.flash {margin-top: 8px;}
172 div.flash {margin-top: 8px;}
171
173
172 div.flash.error, #errorExplanation {
174 div.flash.error, #errorExplanation {
173 background: url(../images/false.png) 8px 5px no-repeat;
175 background: url(../images/false.png) 8px 5px no-repeat;
174 background-color: #ffe3e3;
176 background-color: #ffe3e3;
175 border-color: #dd0000;
177 border-color: #dd0000;
176 color: #550000;
178 color: #550000;
177 }
179 }
178
180
179 div.flash.notice {
181 div.flash.notice {
180 background: url(../images/true.png) 8px 5px no-repeat;
182 background: url(../images/true.png) 8px 5px no-repeat;
181 background-color: #dfffdf;
183 background-color: #dfffdf;
182 border-color: #9fcf9f;
184 border-color: #9fcf9f;
183 color: #005f00;
185 color: #005f00;
184 }
186 }
185
187
186 .nodata {
188 .nodata {
187 text-align: center;
189 text-align: center;
188 background-color: #FFEBC1;
190 background-color: #FFEBC1;
189 border-color: #FDBF3B;
191 border-color: #FDBF3B;
190 color: #A6750C;
192 color: #A6750C;
191 }
193 }
192
194
193 #errorExplanation ul { font-size: 0.9em;}
195 #errorExplanation ul { font-size: 0.9em;}
194
196
195 /***** Ajax indicator ******/
197 /***** Ajax indicator ******/
196 #ajax-indicator {
198 #ajax-indicator {
197 position: absolute; /* fixed not supported by IE */
199 position: absolute; /* fixed not supported by IE */
198 background-color:#eee;
200 background-color:#eee;
199 border: 1px solid #bbb;
201 border: 1px solid #bbb;
200 top:35%;
202 top:35%;
201 left:40%;
203 left:40%;
202 width:20%;
204 width:20%;
203 font-weight:bold;
205 font-weight:bold;
204 text-align:center;
206 text-align:center;
205 padding:0.6em;
207 padding:0.6em;
206 z-index:100;
208 z-index:100;
207 filter:alpha(opacity=50);
209 filter:alpha(opacity=50);
208 -moz-opacity:0.5;
210 -moz-opacity:0.5;
209 opacity: 0.5;
211 opacity: 0.5;
210 -khtml-opacity: 0.5;
212 -khtml-opacity: 0.5;
211 }
213 }
212
214
213 html>body #ajax-indicator { position: fixed; }
215 html>body #ajax-indicator { position: fixed; }
214
216
215 #ajax-indicator span {
217 #ajax-indicator span {
216 background-position: 0% 40%;
218 background-position: 0% 40%;
217 background-repeat: no-repeat;
219 background-repeat: no-repeat;
218 background-image: url(../images/loading.gif);
220 background-image: url(../images/loading.gif);
219 padding-left: 26px;
221 padding-left: 26px;
220 vertical-align: bottom;
222 vertical-align: bottom;
221 }
223 }
222
224
223 /***** Calendar *****/
225 /***** Calendar *****/
224 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
226 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
225 table.cal thead th {width: 14%;}
227 table.cal thead th {width: 14%;}
226 table.cal tbody tr {height: 100px;}
228 table.cal tbody tr {height: 100px;}
227 table.cal th { background-color:#EEEEEE; padding: 4px; }
229 table.cal th { background-color:#EEEEEE; padding: 4px; }
228 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
230 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
229 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
231 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
230 table.cal td.odd p.day-num {color: #bbb;}
232 table.cal td.odd p.day-num {color: #bbb;}
231 table.cal td.today {background:#ffffdd;}
233 table.cal td.today {background:#ffffdd;}
232 table.cal td.today p.day-num {font-weight: bold;}
234 table.cal td.today p.day-num {font-weight: bold;}
233
235
234 /***** Tooltips ******/
236 /***** Tooltips ******/
235 .tooltip{position:relative;z-index:24;}
237 .tooltip{position:relative;z-index:24;}
236 .tooltip:hover{z-index:25;color:#000;}
238 .tooltip:hover{z-index:25;color:#000;}
237 .tooltip span.tip{display: none; text-align:left;}
239 .tooltip span.tip{display: none; text-align:left;}
238
240
239 div.tooltip:hover span.tip{
241 div.tooltip:hover span.tip{
240 display:block;
242 display:block;
241 position:absolute;
243 position:absolute;
242 top:12px; left:24px; width:270px;
244 top:12px; left:24px; width:270px;
243 border:1px solid #555;
245 border:1px solid #555;
244 background-color:#fff;
246 background-color:#fff;
245 padding: 4px;
247 padding: 4px;
246 font-size: 0.8em;
248 font-size: 0.8em;
247 color:#505050;
249 color:#505050;
248 }
250 }
249
251
250 /***** Progress bar *****/
252 /***** Progress bar *****/
251 table.progress {
253 table.progress {
252 border: 1px solid #D7D7D7;
254 border: 1px solid #D7D7D7;
253 border-collapse: collapse;
255 border-collapse: collapse;
254 border-spacing: 0pt;
256 border-spacing: 0pt;
255 empty-cells: show;
257 empty-cells: show;
256 text-align: center;
258 text-align: center;
257 float:left;
259 float:left;
258 margin: 1px 6px 1px 0px;
260 margin: 1px 6px 1px 0px;
259 }
261 }
260
262
261 table.progress td { height: 0.9em; }
263 table.progress td { height: 0.9em; }
262 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
264 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
263 table.progress td.open { background: #FFF none repeat scroll 0%; }
265 table.progress td.open { background: #FFF none repeat scroll 0%; }
264 p.pourcent {font-size: 80%;}
266 p.pourcent {font-size: 80%;}
265 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
267 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
266
268
267 /***** Tabs *****/
269 /***** Tabs *****/
268 #content .tabs{height: 2.6em;}
270 #content .tabs{height: 2.6em;}
269 #content .tabs ul{margin:0;}
271 #content .tabs ul{margin:0;}
270 #content .tabs ul li{
272 #content .tabs ul li{
271 float:left;
273 float:left;
272 list-style-type:none;
274 list-style-type:none;
273 white-space:nowrap;
275 white-space:nowrap;
274 margin-right:8px;
276 margin-right:8px;
275 background:#fff;
277 background:#fff;
276 }
278 }
277 #content .tabs ul li a{
279 #content .tabs ul li a{
278 display:block;
280 display:block;
279 font-size: 0.9em;
281 font-size: 0.9em;
280 text-decoration:none;
282 text-decoration:none;
281 line-height:1em;
283 line-height:1em;
282 padding:4px;
284 padding:4px;
283 border: 1px solid #c0c0c0;
285 border: 1px solid #c0c0c0;
284 }
286 }
285
287
286 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
288 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
287 background-color: #507AAA;
289 background-color: #507AAA;
288 border: 1px solid #507AAA;
290 border: 1px solid #507AAA;
289 color: #fff;
291 color: #fff;
290 text-decoration:none;
292 text-decoration:none;
291 }
293 }
292
294
293 /***** Diff *****/
295 /***** Diff *****/
294 .diff_out { background: #fcc; }
296 .diff_out { background: #fcc; }
295 .diff_in { background: #cfc; }
297 .diff_in { background: #cfc; }
296
298
297 /***** Wiki *****/
299 /***** Wiki *****/
298 div.wiki table {
300 div.wiki table {
299 border: 1px solid #505050;
301 border: 1px solid #505050;
300 border-collapse: collapse;
302 border-collapse: collapse;
301 }
303 }
302
304
303 div.wiki table, div.wiki td, div.wiki th {
305 div.wiki table, div.wiki td, div.wiki th {
304 border: 1px solid #bbb;
306 border: 1px solid #bbb;
305 padding: 4px;
307 padding: 4px;
306 }
308 }
307
309
308 div.wiki .external {
310 div.wiki .external {
309 background-position: 0% 60%;
311 background-position: 0% 60%;
310 background-repeat: no-repeat;
312 background-repeat: no-repeat;
311 padding-left: 12px;
313 padding-left: 12px;
312 background-image: url(../images/external.png);
314 background-image: url(../images/external.png);
313 }
315 }
314
316
315 div.wiki a.new {
317 div.wiki a.new {
316 color: #b73535;
318 color: #b73535;
317 }
319 }
318
320
319 div.wiki pre {
321 div.wiki pre {
320 margin: 1em 1em 1em 1.6em;
322 margin: 1em 1em 1em 1.6em;
321 padding: 2px;
323 padding: 2px;
322 background-color: #fafafa;
324 background-color: #fafafa;
323 border: 1px solid #dadada;
325 border: 1px solid #dadada;
324 width:95%;
326 width:95%;
325 overflow-x: auto;
327 overflow-x: auto;
326 }
328 }
327
329
328 div.wiki div.toc {
330 div.wiki div.toc {
329 background-color: #ffffdd;
331 background-color: #ffffdd;
330 border: 1px solid #e4e4e4;
332 border: 1px solid #e4e4e4;
331 padding: 4px;
333 padding: 4px;
332 line-height: 1.2em;
334 line-height: 1.2em;
333 margin-bottom: 12px;
335 margin-bottom: 12px;
334 margin-right: 12px;
336 margin-right: 12px;
335 display: table
337 display: table
336 }
338 }
337 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
339 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
338
340
339 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
341 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
340 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
342 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
341
343
342 div.wiki div.toc a {
344 div.wiki div.toc a {
343 display: block;
345 display: block;
344 font-size: 0.9em;
346 font-size: 0.9em;
345 font-weight: normal;
347 font-weight: normal;
346 text-decoration: none;
348 text-decoration: none;
347 color: #606060;
349 color: #606060;
348 }
350 }
349 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
351 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
350
352
351 div.wiki div.toc a.heading2 { margin-left: 6px; }
353 div.wiki div.toc a.heading2 { margin-left: 6px; }
352 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
354 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
353
355
354 /***** My page layout *****/
356 /***** My page layout *****/
355 .block-receiver {
357 .block-receiver {
356 border:1px dashed #c0c0c0;
358 border:1px dashed #c0c0c0;
357 margin-bottom: 20px;
359 margin-bottom: 20px;
358 padding: 15px 0 15px 0;
360 padding: 15px 0 15px 0;
359 }
361 }
360
362
361 .mypage-box {
363 .mypage-box {
362 margin:0 0 20px 0;
364 margin:0 0 20px 0;
363 color:#505050;
365 color:#505050;
364 line-height:1.5em;
366 line-height:1.5em;
365 }
367 }
366
368
367 .handle {
369 .handle {
368 cursor: move;
370 cursor: move;
369 }
371 }
370
372
371 a.close-icon {
373 a.close-icon {
372 display:block;
374 display:block;
373 margin-top:3px;
375 margin-top:3px;
374 overflow:hidden;
376 overflow:hidden;
375 width:12px;
377 width:12px;
376 height:12px;
378 height:12px;
377 background-repeat: no-repeat;
379 background-repeat: no-repeat;
378 cursor:pointer;
380 cursor:pointer;
379 background-image:url('../images/close.png');
381 background-image:url('../images/close.png');
380 }
382 }
381
383
382 a.close-icon:hover {
384 a.close-icon:hover {
383 background-image:url('../images/close_hl.png');
385 background-image:url('../images/close_hl.png');
384 }
386 }
385
387
386 /***** Gantt chart *****/
388 /***** Gantt chart *****/
387 .gantt_hdr {
389 .gantt_hdr {
388 position:absolute;
390 position:absolute;
389 top:0;
391 top:0;
390 height:16px;
392 height:16px;
391 border-top: 1px solid #c0c0c0;
393 border-top: 1px solid #c0c0c0;
392 border-bottom: 1px solid #c0c0c0;
394 border-bottom: 1px solid #c0c0c0;
393 border-right: 1px solid #c0c0c0;
395 border-right: 1px solid #c0c0c0;
394 text-align: center;
396 text-align: center;
395 overflow: hidden;
397 overflow: hidden;
396 }
398 }
397
399
398 .task {
400 .task {
399 position: absolute;
401 position: absolute;
400 height:8px;
402 height:8px;
401 font-size:0.8em;
403 font-size:0.8em;
402 color:#888;
404 color:#888;
403 padding:0;
405 padding:0;
404 margin:0;
406 margin:0;
405 line-height:0.8em;
407 line-height:0.8em;
406 }
408 }
407
409
408 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
410 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
409 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
411 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
410 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
412 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
411 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
413 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
412
414
413 /***** Icons *****/
415 /***** Icons *****/
414 .icon {
416 .icon {
415 background-position: 0% 40%;
417 background-position: 0% 40%;
416 background-repeat: no-repeat;
418 background-repeat: no-repeat;
417 padding-left: 20px;
419 padding-left: 20px;
418 padding-top: 2px;
420 padding-top: 2px;
419 padding-bottom: 3px;
421 padding-bottom: 3px;
420 }
422 }
421
423
422 .icon22 {
424 .icon22 {
423 background-position: 0% 40%;
425 background-position: 0% 40%;
424 background-repeat: no-repeat;
426 background-repeat: no-repeat;
425 padding-left: 26px;
427 padding-left: 26px;
426 line-height: 22px;
428 line-height: 22px;
427 vertical-align: middle;
429 vertical-align: middle;
428 }
430 }
429
431
430 .icon-add { background-image: url(../images/add.png); }
432 .icon-add { background-image: url(../images/add.png); }
431 .icon-edit { background-image: url(../images/edit.png); }
433 .icon-edit { background-image: url(../images/edit.png); }
432 .icon-copy { background-image: url(../images/copy.png); }
434 .icon-copy { background-image: url(../images/copy.png); }
433 .icon-del { background-image: url(../images/delete.png); }
435 .icon-del { background-image: url(../images/delete.png); }
434 .icon-move { background-image: url(../images/move.png); }
436 .icon-move { background-image: url(../images/move.png); }
435 .icon-save { background-image: url(../images/save.png); }
437 .icon-save { background-image: url(../images/save.png); }
436 .icon-cancel { background-image: url(../images/cancel.png); }
438 .icon-cancel { background-image: url(../images/cancel.png); }
437 .icon-pdf { background-image: url(../images/pdf.png); }
439 .icon-pdf { background-image: url(../images/pdf.png); }
438 .icon-csv { background-image: url(../images/csv.png); }
440 .icon-csv { background-image: url(../images/csv.png); }
439 .icon-html { background-image: url(../images/html.png); }
441 .icon-html { background-image: url(../images/html.png); }
440 .icon-image { background-image: url(../images/image.png); }
442 .icon-image { background-image: url(../images/image.png); }
441 .icon-txt { background-image: url(../images/txt.png); }
443 .icon-txt { background-image: url(../images/txt.png); }
442 .icon-file { background-image: url(../images/file.png); }
444 .icon-file { background-image: url(../images/file.png); }
443 .icon-folder { background-image: url(../images/folder.png); }
445 .icon-folder { background-image: url(../images/folder.png); }
444 .open .icon-folder { background-image: url(../images/folder_open.png); }
446 .open .icon-folder { background-image: url(../images/folder_open.png); }
445 .icon-package { background-image: url(../images/package.png); }
447 .icon-package { background-image: url(../images/package.png); }
446 .icon-home { background-image: url(../images/home.png); }
448 .icon-home { background-image: url(../images/home.png); }
447 .icon-user { background-image: url(../images/user.png); }
449 .icon-user { background-image: url(../images/user.png); }
448 .icon-mypage { background-image: url(../images/user_page.png); }
450 .icon-mypage { background-image: url(../images/user_page.png); }
449 .icon-admin { background-image: url(../images/admin.png); }
451 .icon-admin { background-image: url(../images/admin.png); }
450 .icon-projects { background-image: url(../images/projects.png); }
452 .icon-projects { background-image: url(../images/projects.png); }
451 .icon-logout { background-image: url(../images/logout.png); }
453 .icon-logout { background-image: url(../images/logout.png); }
452 .icon-help { background-image: url(../images/help.png); }
454 .icon-help { background-image: url(../images/help.png); }
453 .icon-attachment { background-image: url(../images/attachment.png); }
455 .icon-attachment { background-image: url(../images/attachment.png); }
454 .icon-index { background-image: url(../images/index.png); }
456 .icon-index { background-image: url(../images/index.png); }
455 .icon-history { background-image: url(../images/history.png); }
457 .icon-history { background-image: url(../images/history.png); }
456 .icon-feed { background-image: url(../images/feed.png); }
458 .icon-feed { background-image: url(../images/feed.png); }
457 .icon-time { background-image: url(../images/time.png); }
459 .icon-time { background-image: url(../images/time.png); }
458 .icon-stats { background-image: url(../images/stats.png); }
460 .icon-stats { background-image: url(../images/stats.png); }
459 .icon-warning { background-image: url(../images/warning.png); }
461 .icon-warning { background-image: url(../images/warning.png); }
460 .icon-fav { background-image: url(../images/fav.png); }
462 .icon-fav { background-image: url(../images/fav.png); }
461 .icon-fav-off { background-image: url(../images/fav_off.png); }
463 .icon-fav-off { background-image: url(../images/fav_off.png); }
462 .icon-reload { background-image: url(../images/reload.png); }
464 .icon-reload { background-image: url(../images/reload.png); }
463 .icon-lock { background-image: url(../images/locked.png); }
465 .icon-lock { background-image: url(../images/locked.png); }
464 .icon-unlock { background-image: url(../images/unlock.png); }
466 .icon-unlock { background-image: url(../images/unlock.png); }
465 .icon-note { background-image: url(../images/note.png); }
467 .icon-note { background-image: url(../images/note.png); }
466 .icon-checked { background-image: url(../images/true.png); }
468 .icon-checked { background-image: url(../images/true.png); }
467
469
468 .icon22-projects { background-image: url(../images/22x22/projects.png); }
470 .icon22-projects { background-image: url(../images/22x22/projects.png); }
469 .icon22-users { background-image: url(../images/22x22/users.png); }
471 .icon22-users { background-image: url(../images/22x22/users.png); }
470 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
472 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
471 .icon22-role { background-image: url(../images/22x22/role.png); }
473 .icon22-role { background-image: url(../images/22x22/role.png); }
472 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
474 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
473 .icon22-options { background-image: url(../images/22x22/options.png); }
475 .icon22-options { background-image: url(../images/22x22/options.png); }
474 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
476 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
475 .icon22-authent { background-image: url(../images/22x22/authent.png); }
477 .icon22-authent { background-image: url(../images/22x22/authent.png); }
476 .icon22-info { background-image: url(../images/22x22/info.png); }
478 .icon22-info { background-image: url(../images/22x22/info.png); }
477 .icon22-comment { background-image: url(../images/22x22/comment.png); }
479 .icon22-comment { background-image: url(../images/22x22/comment.png); }
478 .icon22-package { background-image: url(../images/22x22/package.png); }
480 .icon22-package { background-image: url(../images/22x22/package.png); }
479 .icon22-settings { background-image: url(../images/22x22/settings.png); }
481 .icon22-settings { background-image: url(../images/22x22/settings.png); }
480 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
482 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
481
483
482 /***** Media print specific styles *****/
484 /***** Media print specific styles *****/
483 @media print {
485 @media print {
484 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
486 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
485 #main { background: #fff; }
487 #main { background: #fff; }
486 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
488 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
487 }
489 }
@@ -1,8 +1,15
1 ---
1 ---
2 repositories_001:
2 repositories_001:
3 project_id: 1
3 project_id: 1
4 url: svn://localhost/test
4 url: svn://localhost/test
5 id: 10
5 id: 10
6 root_url: svn://localhost
6 root_url: svn://localhost
7 password: ""
7 password: ""
8 login: ""
8 login: ""
9 repositories_002:
10 project_id: 2
11 url: svn://localhost/test
12 id: 11
13 root_url: svn://localhost
14 password: ""
15 login: ""
@@ -1,149 +1,149
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'projects_controller'
19 require 'projects_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class ProjectsController; def rescue_action(e) raise e end; end
22 class ProjectsController; def rescue_action(e) raise e end; end
23
23
24 class ProjectsControllerTest < Test::Unit::TestCase
24 class ProjectsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :members, :issues, :journals, :journal_details, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations
25 fixtures :projects, :users, :roles, :members, :issues, :journals, :journal_details, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations
26
26
27 def setup
27 def setup
28 @controller = ProjectsController.new
28 @controller = ProjectsController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 end
31 end
32
32
33 def test_index
33 def test_index
34 get :index
34 get :index
35 assert_response :success
35 assert_response :success
36 assert_template 'list'
36 assert_template 'list'
37 end
37 end
38
38
39 def test_list
39 def test_list
40 get :list
40 get :list
41 assert_response :success
41 assert_response :success
42 assert_template 'list'
42 assert_template 'list'
43 assert_not_nil assigns(:project_tree)
43 assert_not_nil assigns(:project_tree)
44 end
44 end
45
45
46 def test_show
46 def test_show
47 get :show, :id => 1
47 get :show, :id => 1
48 assert_response :success
48 assert_response :success
49 assert_template 'show'
49 assert_template 'show'
50 assert_not_nil assigns(:project)
50 assert_not_nil assigns(:project)
51 end
51 end
52
52
53 def test_list_documents
53 def test_list_documents
54 get :list_documents, :id => 1
54 get :list_documents, :id => 1
55 assert_response :success
55 assert_response :success
56 assert_template 'list_documents'
56 assert_template 'list_documents'
57 assert_not_nil assigns(:grouped)
57 assert_not_nil assigns(:grouped)
58 end
58 end
59
59
60 def test_bulk_edit_issues
60 def test_bulk_edit_issues
61 @request.session[:user_id] = 2
61 @request.session[:user_id] = 2
62 # update issues priority
62 # update issues priority
63 post :bulk_edit_issues, :id => 1, :issue_ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
63 post :bulk_edit_issues, :id => 1, :issue_ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
64 assert_response 302
64 assert_response 302
65 # check that the issues were updated
65 # check that the issues were updated
66 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
66 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
67 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
67 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
68 end
68 end
69
69
70 def test_list_files
70 def test_list_files
71 get :list_files, :id => 1
71 get :list_files, :id => 1
72 assert_response :success
72 assert_response :success
73 assert_template 'list_files'
73 assert_template 'list_files'
74 assert_not_nil assigns(:versions)
74 assert_not_nil assigns(:versions)
75 end
75 end
76
76
77 def test_changelog
77 def test_changelog
78 get :changelog, :id => 1
78 get :changelog, :id => 1
79 assert_response :success
79 assert_response :success
80 assert_template 'changelog'
80 assert_template 'changelog'
81 assert_not_nil assigns(:versions)
81 assert_not_nil assigns(:versions)
82 end
82 end
83
83
84 def test_roadmap
84 def test_roadmap
85 get :roadmap, :id => 1
85 get :roadmap, :id => 1
86 assert_response :success
86 assert_response :success
87 assert_template 'roadmap'
87 assert_template 'roadmap'
88 assert_not_nil assigns(:versions)
88 assert_not_nil assigns(:versions)
89 end
89 end
90
90
91 def test_activity
91 def test_activity
92 get :activity, :id => 1
92 get :activity, :id => 1, :year => 2.days.ago.to_date.year, :month => 2.days.ago.to_date.month
93 assert_response :success
93 assert_response :success
94 assert_template 'activity'
94 assert_template 'activity'
95 assert_not_nil assigns(:events_by_day)
95 assert_not_nil assigns(:events_by_day)
96
96
97 assert_tag :tag => "h3",
97 assert_tag :tag => "h3",
98 :content => /#{2.days.ago.to_date.day}/,
98 :content => /#{2.days.ago.to_date.day}/,
99 :sibling => { :tag => "ul",
99 :sibling => { :tag => "ul",
100 :child => { :tag => "li",
100 :child => { :tag => "li",
101 :child => { :tag => "p",
101 :child => { :tag => "p",
102 :content => /(#{IssueStatus.find(2).name})/,
102 :content => /(#{IssueStatus.find(2).name})/,
103 }
103 }
104 }
104 }
105 }
105 }
106 assert_tag :tag => "h3",
106 assert_tag :tag => "h3",
107 :content => /#{3.day.ago.to_date.day}/,
107 :content => /#{3.day.ago.to_date.day}/,
108 :sibling => { :tag => "ul", :child => { :tag => "li",
108 :sibling => { :tag => "ul", :child => { :tag => "li",
109 :child => { :tag => "p",
109 :child => { :tag => "p",
110 :content => /#{Issue.find(1).subject}/,
110 :content => /#{Issue.find(1).subject}/,
111 }
111 }
112 }
112 }
113 }
113 }
114 end
114 end
115
115
116 def test_archive
116 def test_archive
117 @request.session[:user_id] = 1 # admin
117 @request.session[:user_id] = 1 # admin
118 post :archive, :id => 1
118 post :archive, :id => 1
119 assert_redirected_to 'admin/projects'
119 assert_redirected_to 'admin/projects'
120 assert !Project.find(1).active?
120 assert !Project.find(1).active?
121 end
121 end
122
122
123 def test_unarchive
123 def test_unarchive
124 @request.session[:user_id] = 1 # admin
124 @request.session[:user_id] = 1 # admin
125 Project.find(1).archive
125 Project.find(1).archive
126 post :unarchive, :id => 1
126 post :unarchive, :id => 1
127 assert_redirected_to 'admin/projects'
127 assert_redirected_to 'admin/projects'
128 assert Project.find(1).active?
128 assert Project.find(1).active?
129 end
129 end
130
130
131 def test_add_issue
131 def test_add_issue
132 @request.session[:user_id] = 2
132 @request.session[:user_id] = 2
133 get :add_issue, :id => 1, :tracker_id => 1
133 get :add_issue, :id => 1, :tracker_id => 1
134 assert_response :success
134 assert_response :success
135 assert_template 'add_issue'
135 assert_template 'add_issue'
136 post :add_issue, :id => 1, :issue => {:tracker_id => 1, :subject => 'This is the test_add_issue issue', :description => 'This is the description', :priority_id => 5}
136 post :add_issue, :id => 1, :issue => {:tracker_id => 1, :subject => 'This is the test_add_issue issue', :description => 'This is the description', :priority_id => 5}
137 assert_redirected_to 'projects/1/issues'
137 assert_redirected_to 'projects/1/issues'
138 assert Issue.find_by_subject('This is the test_add_issue issue')
138 assert Issue.find_by_subject('This is the test_add_issue issue')
139 end
139 end
140
140
141 def test_copy_issue
141 def test_copy_issue
142 @request.session[:user_id] = 2
142 @request.session[:user_id] = 2
143 get :add_issue, :id => 1, :copy_from => 1
143 get :add_issue, :id => 1, :copy_from => 1
144 assert_template 'add_issue'
144 assert_template 'add_issue'
145 assert_not_nil assigns(:issue)
145 assert_not_nil assigns(:issue)
146 orig = Issue.find(1)
146 orig = Issue.find(1)
147 assert_equal orig.subject, assigns(:issue).subject
147 assert_equal orig.subject, assigns(:issue).subject
148 end
148 end
149 end
149 end
@@ -1,42 +1,62
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class ChangesetTest < Test::Unit::TestCase
20 class ChangesetTest < Test::Unit::TestCase
21 fixtures :projects, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :users, :members, :trackers
21 fixtures :projects, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :users, :members, :trackers
22
22
23 def setup
23 def setup
24 end
24 end
25
25
26 def test_ref_keywords_any
26 def test_ref_keywords_any
27 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
27 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
28 Setting.commit_fix_done_ratio = '90'
28 Setting.commit_fix_done_ratio = '90'
29 Setting.commit_ref_keywords = '*'
29 Setting.commit_ref_keywords = '*'
30 Setting.commit_fix_keywords = 'fixes , closes'
30 Setting.commit_fix_keywords = 'fixes , closes'
31
31
32 c = Changeset.new(:repository => Project.find(1).repository,
32 c = Changeset.new(:repository => Project.find(1).repository,
33 :committed_on => Time.now,
33 :committed_on => Time.now,
34 :comments => 'New commit (#2). Fixes #1')
34 :comments => 'New commit (#2). Fixes #1')
35 c.scan_comment_for_issue_ids
35 c.scan_comment_for_issue_ids
36
36
37 assert_equal [1, 2], c.issue_ids.sort
37 assert_equal [1, 2], c.issue_ids.sort
38 fixed = Issue.find(1)
38 fixed = Issue.find(1)
39 assert fixed.closed?
39 assert fixed.closed?
40 assert_equal 90, fixed.done_ratio
40 assert_equal 90, fixed.done_ratio
41 end
41 end
42
43 def test_previous
44 changeset = Changeset.find_by_revision(3)
45 assert_equal Changeset.find_by_revision(2), changeset.previous
46 end
47
48 def test_previous_nil
49 changeset = Changeset.find_by_revision(1)
50 assert_nil changeset.previous
51 end
52
53 def test_next
54 changeset = Changeset.find_by_revision(2)
55 assert_equal Changeset.find_by_revision(3), changeset.next
56 end
57
58 def test_next_nil
59 changeset = Changeset.find_by_revision(4)
60 assert_nil changeset.next
61 end
42 end
62 end
@@ -1,76 +1,76
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class RepositoryTest < Test::Unit::TestCase
20 class RepositoryTest < Test::Unit::TestCase
21 fixtures :projects, :repositories, :issues, :issue_statuses, :changesets, :changes
21 fixtures :projects, :repositories, :issues, :issue_statuses, :changesets, :changes
22
22
23 def setup
23 def setup
24 @repository = Project.find(1).repository
24 @repository = Project.find(1).repository
25 end
25 end
26
26
27 def test_create
27 def test_create
28 repository = Repository::Subversion.new(:project => Project.find(2))
28 repository = Repository::Subversion.new(:project => Project.find(3))
29 assert !repository.save
29 assert !repository.save
30
30
31 repository.url = "svn://localhost"
31 repository.url = "svn://localhost"
32 assert repository.save
32 assert repository.save
33 repository.reload
33 repository.reload
34
34
35 project = Project.find(2)
35 project = Project.find(3)
36 assert_equal repository, project.repository
36 assert_equal repository, project.repository
37 end
37 end
38
38
39 def test_scan_changesets_for_issue_ids
39 def test_scan_changesets_for_issue_ids
40 # choosing a status to apply to fix issues
40 # choosing a status to apply to fix issues
41 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
41 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
42 Setting.commit_fix_done_ratio = "90"
42 Setting.commit_fix_done_ratio = "90"
43 Setting.commit_ref_keywords = 'refs , references, IssueID'
43 Setting.commit_ref_keywords = 'refs , references, IssueID'
44 Setting.commit_fix_keywords = 'fixes , closes'
44 Setting.commit_fix_keywords = 'fixes , closes'
45
45
46 # make sure issue 1 is not already closed
46 # make sure issue 1 is not already closed
47 assert !Issue.find(1).status.is_closed?
47 assert !Issue.find(1).status.is_closed?
48
48
49 Repository.scan_changesets_for_issue_ids
49 Repository.scan_changesets_for_issue_ids
50 assert_equal [101, 102], Issue.find(3).changeset_ids
50 assert_equal [101, 102], Issue.find(3).changeset_ids
51
51
52 # fixed issues
52 # fixed issues
53 fixed_issue = Issue.find(1)
53 fixed_issue = Issue.find(1)
54 assert fixed_issue.status.is_closed?
54 assert fixed_issue.status.is_closed?
55 assert_equal 90, fixed_issue.done_ratio
55 assert_equal 90, fixed_issue.done_ratio
56 assert_equal [101], fixed_issue.changeset_ids
56 assert_equal [101], fixed_issue.changeset_ids
57
57
58 # ignoring commits referencing an issue of another project
58 # ignoring commits referencing an issue of another project
59 assert_equal [], Issue.find(4).changesets
59 assert_equal [], Issue.find(4).changesets
60 end
60 end
61
61
62 def test_for_changeset_comments_strip
62 def test_for_changeset_comments_strip
63 repository = Repository::Mercurial.create( :project => Project.find( 4 ), :url => '/foo/bar/baz' )
63 repository = Repository::Mercurial.create( :project => Project.find( 4 ), :url => '/foo/bar/baz' )
64 comment = <<-COMMENT
64 comment = <<-COMMENT
65 This is a loooooooooooooooooooooooooooong comment
65 This is a loooooooooooooooooooooooooooong comment
66
66
67
67
68 COMMENT
68 COMMENT
69 changeset = Changeset.new(
69 changeset = Changeset.new(
70 :comments => comment, :commit_date => Time.now, :revision => 0, :scmid => 'f39b7922fb3c',
70 :comments => comment, :commit_date => Time.now, :revision => 0, :scmid => 'f39b7922fb3c',
71 :committer => 'foo <foo@example.com>', :committed_on => Time.now, :repository => repository )
71 :committer => 'foo <foo@example.com>', :committed_on => Time.now, :repository => repository )
72 assert( changeset.save )
72 assert( changeset.save )
73 assert_not_equal( comment, changeset.comments )
73 assert_not_equal( comment, changeset.comments )
74 assert_equal( 'This is a loooooooooooooooooooooooooooong comment', changeset.comments )
74 assert_equal( 'This is a loooooooooooooooooooooooooooong comment', changeset.comments )
75 end
75 end
76 end
76 end
General Comments 0
You need to be logged in to leave comments. Login now