##// END OF EJS Templates
svn browser merged in trunk...
Jean-Philippe Lang -
r103:bc4415850105
parent child
Show More
@@ -0,0 +1,72
1 # redMine - project management software
2 # Copyright (C) 2006 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 class RepositoriesController < ApplicationController
19 layout 'base'
20 before_filter :find_project, :authorize
21
22 def show
23 @entries = @repository.scm.entries('')
24 show_error and return unless @entries
25 @latest_revision = @entries.revisions.latest
26 end
27
28 def browse
29 @entries = @repository.scm.entries(@path, @rev)
30 show_error and return unless @entries
31 end
32
33 def revisions
34 @entry = @repository.scm.entry(@path, @rev)
35 @revisions = @repository.scm.revisions(@path, @rev)
36 show_error and return unless @entry && @revisions
37 end
38
39 def entry
40 if 'raw' == params[:format]
41 content = @repository.scm.cat(@path, @rev)
42 show_error and return unless content
43 send_data content, :filename => @path.split('/').last
44 end
45 end
46
47 def revision
48 @revisions = @repository.scm.revisions '', @rev, @rev, :with_paths => true
49 show_error and return unless @revisions
50 @revision = @revisions.first
51 end
52
53 def diff
54 @rev_to = params[:rev_to] || (@rev-1)
55 @diff = @repository.scm.diff(params[:path], @rev, @rev_to)
56 show_error and return unless @diff
57 end
58
59 private
60 def find_project
61 @project = Project.find(params[:id])
62 @repository = @project.repository
63 @path = params[:path].squeeze('/').gsub(/^\//, '') if params[:path]
64 @path ||= ''
65 @rev = params[:rev].to_i if params[:rev] and params[:rev].to_i > 0
66 end
67
68 def show_error
69 flash.now[:notice] = l(:notice_scm_error)
70 render :nothing => true, :layout => true
71 end
72 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006 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 module RepositoriesHelper
19 end
@@ -0,0 +1,28
1 # redMine - project management software
2 # Copyright (C) 2006 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 class Repository < ActiveRecord::Base
19 belongs_to :project
20 validates_presence_of :url
21 validates_format_of :url, :with => /^(http|https|svn):\/\/.+/i
22
23 @scm = nil
24
25 def scm
26 @scm ||= SvnRepos::Base.new url
27 end
28 end
@@ -0,0 +1,214
1 # redMine - project management software
2 # Copyright (C) 2006 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 'rexml/document'
19
20 module SvnRepos
21
22 class CommandFailed < StandardError #:nodoc:
23 end
24
25 class Base
26 @url = nil
27 @login = nil
28 @password = nil
29
30 def initialize(url, login=nil, password=nil)
31 @url = url
32 @login = login if login && !login.empty?
33 @password = (password || "") if @login
34 end
35
36 # Returns the entry identified by path and revision identifier
37 # or nil if entry doesn't exist in the repository
38 def entry(path=nil, identifier=nil)
39 e = entries(path, identifier)
40 e ? e.first : nil
41 end
42
43 # Returns an Entries collection
44 # or nil if the given path doesn't exist in the repository
45 def entries(path=nil, identifier=nil)
46 path ||= ''
47 identifier = 'HEAD' unless identifier and identifier > 0
48 entries = Entries.new
49 cmd = "svn list --xml #{target(path)}@#{identifier}"
50 shellout(cmd) do |io|
51 begin
52 doc = REXML::Document.new(io)
53 doc.elements.each("lists/list/entry") do |entry|
54 entries << Entry.new({:name => entry.elements['name'].text,
55 :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
56 :kind => entry.attributes['kind'],
57 :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
58 :lastrev => Revision.new({
59 :identifier => entry.elements['commit'].attributes['revision'],
60 :time => Time.parse(entry.elements['commit'].elements['date'].text),
61 :author => entry.elements['commit'].elements['author'].text
62 })
63 })
64 end
65 rescue
66 end
67 end
68 return nil if $? && $?.exitstatus != 0
69 entries.sort_by_name
70 rescue Errno::ENOENT => e
71 raise CommandFailed
72 end
73
74 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
75 path ||= ''
76 identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0
77 identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
78 revisions = Revisions.new
79 cmd = "svn log --xml -r #{identifier_from}:#{identifier_to} "
80 cmd << "--verbose " if options[:with_paths]
81 cmd << target(path)
82 shellout(cmd) do |io|
83 begin
84 doc = REXML::Document.new(io)
85 doc.elements.each("log/logentry") do |logentry|
86 paths = []
87 logentry.elements.each("paths/path") do |path|
88 paths << {:action => path.attributes['action'],
89 :path => path.text
90 }
91 end
92 paths.sort! { |x,y| x[:path] <=> y[:path] }
93
94 revisions << Revision.new({:identifier => logentry.attributes['revision'],
95 :author => logentry.elements['author'].text,
96 :time => Time.parse(logentry.elements['date'].text),
97 :message => logentry.elements['msg'].text,
98 :paths => paths
99 })
100 end
101 rescue
102 end
103 end
104 return nil if $? && $?.exitstatus != 0
105 revisions
106 rescue Errno::ENOENT => e
107 raise CommandFailed
108 end
109
110 def diff(path, identifier_from, identifier_to=nil)
111 path ||= ''
112 if identifier_to and identifier_to.to_i > 0
113 identifier_to = identifier_to.to_i
114 else
115 identifier_to = identifier_from.to_i - 1
116 end
117 cmd = "svn diff -r "
118 cmd << "#{identifier_to}:"
119 cmd << "#{identifier_from}"
120 cmd << "#{target(path)}@#{identifier_from}"
121 diff = []
122 shellout(cmd) do |io|
123 io.each_line do |line|
124 diff << line
125 end
126 end
127 return nil if $? && $?.exitstatus != 0
128 diff
129 rescue Errno::ENOENT => e
130 raise CommandFailed
131 end
132
133 def cat(path, identifier=nil)
134 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
135 cmd = "svn cat #{target(path)}@#{identifier}"
136 cat = nil
137 shellout(cmd) do |io|
138 cat = io.read
139 end
140 return nil if $? && $?.exitstatus != 0
141 cat
142 rescue Errno::ENOENT => e
143 raise CommandFailed
144 end
145
146 private
147 def target(path)
148 " \"" << "#{@url}/#{path}".gsub(/["'?<>\*]/, '') << "\""
149 end
150
151 def logger
152 RAILS_DEFAULT_LOGGER
153 end
154
155 def shellout(cmd, &block)
156 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
157 IO.popen(cmd) do |io|
158 block.call(io) if block_given?
159 end
160 end
161 end
162
163 class Entries < Array
164 def sort_by_name
165 sort {|x,y|
166 if x.kind == y.kind
167 x.name <=> y.name
168 else
169 x.kind <=> y.kind
170 end
171 }
172 end
173
174 def revisions
175 revisions ||= Revisions.new(collect{|entry| entry.lastrev})
176 end
177 end
178
179 class Entry
180 attr_accessor :name, :path, :kind, :size, :lastrev
181 def initialize(attributes={})
182 self.name = attributes[:name] if attributes[:name]
183 self.path = attributes[:path] if attributes[:path]
184 self.kind = attributes[:kind] if attributes[:kind]
185 self.size = attributes[:size].to_i if attributes[:size]
186 self.lastrev = attributes[:lastrev]
187 end
188
189 def is_file?
190 'file' == self.kind
191 end
192
193 def is_dir?
194 'dir' == self.kind
195 end
196 end
197
198 class Revisions < Array
199 def latest
200 sort {|x,y| x.time <=> y.time}.last
201 end
202 end
203
204 class Revision
205 attr_accessor :identifier, :author, :time, :message, :paths
206 def initialize(attributes={})
207 self.identifier = attributes[:identifier]
208 self.author = attributes[:author]
209 self.time = attributes[:time]
210 self.message = attributes[:message] || ""
211 self.paths = attributes[:paths]
212 end
213 end
214 end No newline at end of file
@@ -0,0 +1,23
1 <table class="list">
2 <thead><tr>
3 <th><%= l(:field_name) %></th>
4 <th><%= l(:field_filesize) %></th>
5 <th><%= l(:label_revision) %></th>
6 <th><%= l(:field_author) %></th>
7 <th><%= l(:label_date) %></th>
8 </tr></thead>
9 <tbody>
10 <% total_size = 0
11 @entries.each do |entry| %>
12 <tr class="<%= cycle 'odd', 'even' %>">
13 <td><%= link_to h(entry.name), { :action => (entry.is_dir? ? 'browse' : 'revisions'), :id => @project, :path => entry.path, :rev => @rev }, :class => "icon " + (entry.is_dir? ? 'folder' : 'file') %></td>
14 <td align="right"><%= human_size(entry.size) unless entry.is_dir? %></td>
15 <td align="right"><%= link_to entry.lastrev.identifier, :action => 'revision', :id => @project, :rev => entry.lastrev.identifier %></td>
16 <td align="center"><em><%=h entry.lastrev.author %></em></td>
17 <td align="center"><%= format_time(entry.lastrev.time) %></td>
18 </tr>
19 <% total_size += entry.size
20 end %>
21 </tbody>
22 </table>
23 <p align="right"><em><%= l(:label_total) %>: <%= human_size(total_size) %></em></p> No newline at end of file
@@ -0,0 +1,18
1 <%= link_to 'root', :action => 'browse', :id => @project, :path => '', :rev => @rev %>
2 <%
3 dirs = path.split('/')
4 if 'file' == kind
5 filename = dirs.pop
6 end
7 link_path = ''
8 dirs.each do |dir|
9 link_path << '/' unless link_path.empty?
10 link_path << "#{dir}"
11 %>
12 / <%= link_to h(dir), :action => 'browse', :id => @project, :path => link_path, :rev => @rev %>
13 <% end %>
14 <% if filename %>
15 / <%= link_to h(filename), :action => 'revisions', :id => @project, :path => "#{link_path}/#{filename}", :rev => @rev %>
16 <% end %>
17
18 <%= "@ #{revision}" if revision %> No newline at end of file
@@ -0,0 +1,11
1 <%= stylesheet_link_tag "scm" %>
2
3 <div class="contextual">
4 <%= start_form_tag %>
5 <%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %>
6 <%= submit_tag 'OK' %>
7 </div>
8
9 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %></h2>
10
11 <%= render :partial => 'dir_list' %> No newline at end of file
@@ -0,0 +1,55
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
2
3 <%= stylesheet_link_tag "scm" %>
4
5 <table class="list">
6 <thead><tr><th>@<%= @rev %></th><th>@<%= @rev_to %></th><th></th></tr></thead>
7 <tbody>
8 <% parsing = false
9 line_num_l = 0
10 line_num_r = 0 %>
11 <% @diff.each do |line| %>
12 <%
13 if line =~ /^@@ (\+|\-)(\d+),\d+ (\+|\-)(\d+),\d+ @@/
14 line_num_l = $2.to_i
15 line_num_r = $4.to_i
16 if parsing %>
17 <tr class="spacing"><td colspan="3">&nbsp;</td></tr>
18 <% end
19 parsing = true
20 next
21 end
22 next unless parsing
23 %>
24
25 <tr>
26
27 <% case line[0, 1]
28 when " " %>
29 <th class="line-num"><%= line_num_l %></th>
30 <th class="line-num"><%= line_num_r %></th>
31 <td class="line-code">
32 <% line_num_l = line_num_l + 1
33 line_num_r = line_num_r + 1
34
35 when "-" %>
36 <th class="line-num"></th>
37 <th class="line-num"><%= line_num_r %></th>
38 <td class="line-code" style="background: #fdd;">
39 <% line_num_r = line_num_r + 1
40
41 when "+" %>
42 <th class="line-num"><%= line_num_l %></th>
43 <th class="line-num"></th>
44 <td class="line-code" style="background: #dfd;">
45 <% line_num_l = line_num_l + 1
46
47 else
48 next
49 end %>
50
51 <%= h(line[1..-1]).gsub(/\s/, "&nbsp;") %></td></tr>
52
53 <% end %>
54 </tbody>
55 </table> No newline at end of file
@@ -0,0 +1,35
1 <%= stylesheet_link_tag "scm" %>
2
3 <div class="contextual">
4 <%= start_form_tag %>
5 <%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %>
6 <%= submit_tag 'OK' %>
7 </div>
8
9 <h2><%= l(:label_revision) %> <%= @revision.identifier %></h2>
10
11 <p><em><%= @revision.author %>, <%= format_time(@revision.time) %></em></p>
12 <%= simple_format @revision.message %>
13
14 <div style="float:right;">
15 <div class="square action_A"></div> <div style="float:left;"><%= l(:label_added) %>&nbsp;</div>
16 <div class="square action_M"></div> <div style="float:left;"><%= l(:label_modified) %>&nbsp;</div>
17 <div class="square action_D"></div> <div style="float:left;"><%= l(:label_deleted) %>&nbsp;</div>
18 </div>
19
20 <h3><%= l(:label_attachment_plural) %></h3>
21 <table class="list">
22 <tbody>
23 <% @revision.paths.each do |path| %>
24 <tr class="<%= cycle 'odd', 'even' %>">
25 <td><div class="square action_<%= path[:action] %>"></div> <%= path[:path] %></td>
26 <td>
27 <% if path[:action] == "M" %>
28 <%= link_to 'View diff', :action => 'diff', :id => @project, :path => path[:path].gsub(/^\//, ''), :rev => @revision.identifier %>
29 <% end %>
30 </td>
31 </tr>
32 <% end %>
33 </tbody>
34 </table>
35 <p><%= lwr(:label_modification, @revision.paths.length) %></p> No newline at end of file
@@ -0,0 +1,38
1 <%= stylesheet_link_tag "scm" %>
2
3 <div class="contextual">
4 <%= start_form_tag %>
5 <%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %>
6 <%= submit_tag 'OK' %>
7 </div>
8
9 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => @entry.kind, :revision => @rev } %></h2>
10
11 <% if @entry.is_file? %>
12 <h3><%=h @entry.name %></h3>
13 <p><%= link_to 'Download', {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' }, :class => "icon file" %> (<%= human_size @entry.size %>)</p>
14 <% end %>
15
16 <h3>Revisions</h3>
17
18 <table class="list">
19 <thead><tr>
20 <th>#</th>
21 <th><%= l(:field_author) %></th>
22 <th><%= l(:label_date) %></th>
23 <th><%= l(:field_description) %></th>
24 <th></th>
25 </tr></thead>
26 <tbody>
27 <% @revisions.each do |revision| %>
28 <tr class="<%= cycle 'odd', 'even' %>">
29 <th align="center"><%= link_to revision.identifier, :action => 'revision', :id => @project, :rev => revision.identifier %></th>
30 <td align="center"><em><%=h revision.author %></em></td>
31 <td align="center"><%= format_time(revision.time) %></td>
32 <td width="70%"><%= simple_format(h(revision.message)) %></td>
33 <td align="center"><%= link_to 'Diff', :action => 'diff', :id => @project, :path => @path, :rev => revision.identifier if @entry.is_file? && revision != @revisions.last %></td>
34 </tr>
35 <% end %>
36 </tbody>
37 </table>
38 <p><%= lwr(:label_modification, @revisions.length) %></p> No newline at end of file
@@ -0,0 +1,15
1 <%= stylesheet_link_tag "scm" %>
2
3 <h2><%= l(:label_repository) %></h2>
4
5 <h3><%= l(:label_revision_plural) %></h3>
6 <% if @latest_revision %>
7 <p><%= l(:label_latest_revision) %>:
8 <%= link_to @latest_revision.identifier, :action => 'revision', :id => @project, :rev => @latest_revision.identifier %><br />
9 <em><%= @latest_revision.author %>, <%= format_time(@latest_revision.time) %></em></p>
10 <% end %>
11 <p><%= link_to l(:label_view_revisions), :action => 'revisions', :id => @project %></p>
12
13
14 <h3><%= l(:label_browse) %></h3>
15 <%= render :partial => 'dir_list' %> No newline at end of file
@@ -0,0 +1,12
1 class CreateRepositories < ActiveRecord::Migration
2 def self.up
3 create_table :repositories, :force => true do |t|
4 t.column "project_id", :integer, :default => 0, :null => false
5 t.column "url", :string, :default => "", :null => false
6 end
7 end
8
9 def self.down
10 drop_table :repositories
11 end
12 end
@@ -0,0 +1,19
1 class AddRepositoriesPermissions < ActiveRecord::Migration
2 def self.up
3 Permission.create :controller => "repositories", :action => "show", :description => "button_view", :sort => 1450, :is_public => true
4 Permission.create :controller => "repositories", :action => "browse", :description => "label_browse", :sort => 1460, :is_public => true
5 Permission.create :controller => "repositories", :action => "entry", :description => "entry", :sort => 1462, :is_public => true
6 Permission.create :controller => "repositories", :action => "revisions", :description => "label_view_revisions", :sort => 1470, :is_public => true
7 Permission.create :controller => "repositories", :action => "revision", :description => "label_view_revisions", :sort => 1472, :is_public => true
8 Permission.create :controller => "repositories", :action => "diff", :description => "diff", :sort => 1480, :is_public => true
9 end
10
11 def self.down
12 Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'show']).destroy
13 Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'browse']).destroy
14 Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'entry']).destroy
15 Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'revisions']).destroy
16 Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'revision']).destroy
17 Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'diff']).destroy
18 end
19 end
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,66
1
2
3 div.square {
4 border: 1px solid #999;
5 float: left;
6 margin: .4em .5em 0 0;
7 overflow: hidden;
8 width: .6em; height: .6em;
9 }
10
11 div.action_M { background: #fd8 }
12 div.action_D { background: #f88 }
13 div.action_A { background: #bfb }
14
15 table.list {
16 width:100%;
17 border-collapse: collapse;
18 border: 1px dotted #d0d0d0;
19 margin-bottom: 6px;
20 }
21
22 table.list thead th {
23 text-align: center;
24 background: #eee;
25 border: 1px solid #d7d7d7;
26 }
27
28 table.list tbody th {
29 font-weight: normal;
30 text-align: center;
31 background: #eed;
32 border: 1px solid #d7d7d7;
33 }
34
35 .icon {
36 background-position: 0% 40%;
37 background-repeat: no-repeat;
38 padding-left: 20px;
39 }
40
41 .folder { background-image: url(../images/folder.png); }
42 .file { background-image: url(../images/file.png); }
43
44
45
46 tr.spacing {
47 border: 1px solid #d7d7d7;
48 }
49
50 .line-num {
51 border: 1px solid #d7d7d7;
52 font-size: 0.8em;
53 text-align: right;
54 width: 3em;
55 padding-right: 3px;
56 }
57
58 .line-code {
59 font-family: "Courier New", monospace;
60 font-size: 1em;
61 }
62
63 table p {
64 margin:0;
65 padding:0;
66 } No newline at end of file
@@ -62,6 +62,10 class ProjectsController < ApplicationController
62 62 @project.custom_fields = CustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids]
63 63 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
64 64 @project.custom_values = @custom_values
65 if params[:repository_enabled] && params[:repository_enabled] == "1"
66 @project.repository = Repository.new
67 @project.repository.attributes = params[:repository]
68 end
65 69 if @project.save
66 70 flash[:notice] = l(:notice_successful_create)
67 71 redirect_to :controller => 'admin', :action => 'projects'
@@ -96,7 +100,17 class ProjectsController < ApplicationController
96 100 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
97 101 @project.custom_values = @custom_values
98 102 end
99 if @project.update_attributes(params[:project])
103 if params[:repository_enabled]
104 case params[:repository_enabled]
105 when "0"
106 @project.repository = nil
107 when "1"
108 @project.repository ||= Repository.new
109 @project.repository.attributes = params[:repository]
110 end
111 end
112 @project.attributes = params[:project]
113 if @project.save
100 114 flash[:notice] = l(:notice_successful_update)
101 115 redirect_to :action => 'settings', :id => @project
102 116 else
@@ -164,7 +164,7 class TabularFormBuilder < ActionView::Helpers::FormBuilder
164 164 return super if options.delete :no_label
165 165 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
166 166 label = @template.content_tag("label", label_text,
167 :class => (@object.errors[field] ? "error" : nil),
167 :class => (@object && @object.errors[field] ? "error" : nil),
168 168 :for => (@object_name.to_s + "_" + field.to_s))
169 169 label + super
170 170 end
@@ -175,7 +175,7 class TabularFormBuilder < ActionView::Helpers::FormBuilder
175 175 def select(field, choices, options = {}, html_options = {})
176 176 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
177 177 label = @template.content_tag("label", label_text,
178 :class => (@object.errors[field] ? "error" : nil),
178 :class => (@object && @object.errors[field] ? "error" : nil),
179 179 :for => (@object_name.to_s + "_" + field.to_s))
180 180 label + super
181 181 end
@@ -30,6 +30,7 class Permission < ActiveRecord::Base
30 30 1100 => :label_news_plural,
31 31 1200 => :label_document_plural,
32 32 1300 => :label_attachment_plural,
33 1400 => :label_repository
33 34 }.freeze
34 35
35 36 @@cached_perms_for_public = nil
@@ -25,12 +25,14 class Project < ActiveRecord::Base
25 25 has_many :documents, :dependent => true
26 26 has_many :news, :dependent => true, :include => :author
27 27 has_many :issue_categories, :dependent => true, :order => "issue_categories.name"
28 has_one :repository, :dependent => true
28 29 has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => 'custom_fields_projects', :association_foreign_key => 'custom_field_id'
29 30 acts_as_tree :order => "name", :counter_cache => true
30 31
31 32 validates_presence_of :name, :description
32 33 validates_uniqueness_of :name
33 34 validates_associated :custom_values, :on => :update
35 validates_associated :repository
34 36 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
35 37
36 38 # returns 5 last created projects
@@ -91,6 +91,7
91 91 <%= link_to l(:label_document_plural), {:controller => 'projects', :action => 'list_documents', :id => @project }, :class => "menuItem" %>
92 92 <%= link_to l(:label_member_plural), {:controller => 'projects', :action => 'list_members', :id => @project }, :class => "menuItem" %>
93 93 <%= link_to l(:label_attachment_plural), {:controller => 'projects', :action => 'list_files', :id => @project }, :class => "menuItem" %>
94 <%= link_to l(:label_repository), {:controller => 'repositories', :action => 'show', :id => @project}, :class => "menuItem" if @project.repository and !@project.repository.new_record? %></li>
94 95 <%= link_to_if_authorized l(:label_settings), {:controller => 'projects', :action => 'settings', :id => @project }, :class => "menuItem" %>
95 96 </div>
96 97 <% end %>
@@ -112,6 +113,7
112 113 <li><%= link_to l(:label_document_plural), :controller => 'projects', :action => 'list_documents', :id => @project %></li>
113 114 <li><%= link_to l(:label_member_plural), :controller => 'projects', :action => 'list_members', :id => @project %></li>
114 115 <li><%= link_to l(:label_attachment_plural), :controller => 'projects', :action => 'list_files', :id => @project %></li>
116 <li><%= link_to l(:label_repository), :controller => 'repositories', :action => 'show', :id => @project if @project.repository and !@project.repository.new_record? %></li>
115 117 <li><%= link_to_if_authorized l(:label_settings), :controller => 'projects', :action => 'settings', :id => @project %></li>
116 118 </ul>
117 119 <% end %>
@@ -1,4 +1,5
1 1 <%= error_messages_for 'project' %>
2
2 3 <div class="box">
3 4 <!--[form:project]-->
4 5 <p><%= f.text_field :name, :required => true %></p>
@@ -22,5 +23,15
22 23 <%= custom_field.name %>
23 24 <% end %></p>
24 25 <% end %>
25 <!--[eoform:project]-->
26 <!--[eoform:project]-->
27 </div>
28
29 <div class="box"><h3><%= check_box_tag "repository_enabled", 1, !@project.repository.nil?, :onclick => "Element.toggle('repository');" %> <%= l(:label_repository) %></h3>
30 <%= hidden_field_tag "repository_enabled", 0 %>
31 <div id="repository">
32 <% fields_for :repository, @project.repository, { :builder => TabularFormBuilder, :lang => current_language} do |repository| %>
33 <p><%= repository.text_field :url, :size => 60, :required => true %><br />(http://, https://, svn://)</p>
34 <% end %>
35 </div>
36 <%= javascript_tag "Element.hide('repository');" if @project.repository.nil? %>
26 37 </div>
@@ -9,7 +9,8 ActionController::Routing::Routes.draw do |map|
9 9 # You can have the root of your site routed by hooking up ''
10 10 # -- just remember to delete public/index.html.
11 11 map.connect '', :controller => "welcome"
12
12
13 map.connect 'repositories/:action/:id/:path', :controller => 'repositories'
13 14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
14 15 map.connect 'help/:ctrl/:page', :controller => 'help'
15 16 map.connect ':controller/:action/:id/:sort_key/:sort_order'
@@ -7,6 +7,7 http://redmine.org/
7 7
8 8 == xx/xx/2006 v0.x.x
9 9
10 * simple SVN browser added (just needs svn binaries in PATH)
10 11 * comments can now be added on news
11 12 * "my page" is now customizable
12 13 * more powerfull and savable filters for issues lists
@@ -63,6 +63,7 notice_successful_delete: Erfolgreiche Auslassung.
63 63 notice_successful_connection: Erfolgreicher Anschluß.
64 64 notice_file_not_found: Erbetene Akte besteht nicht oder ist gelöscht worden.
65 65 notice_locking_conflict: Data have been updated by another user.
66 notice_scm_error: Eintragung und/oder Neuausgabe besteht nicht im Behälter.
66 67
67 68 mail_subject_lost_password: Dein redMine Kennwort
68 69 mail_subject_register: redMine Kontoaktivierung
@@ -136,6 +137,7 field_start_date: Beginn
136 137 field_done_ratio: %% Getan
137 138 field_hide_mail: Mein email address verstecken
138 139 field_comment: Anmerkung
140 field_url: URL
139 141
140 142 label_user: Benutzer
141 143 label_user_plural: Benutzer
@@ -282,6 +284,17 label_ago: vor
282 284 label_contains: enthält
283 285 label_not_contains: enthält nicht
284 286 label_day_plural: Tage
287 label_repository: SVN Behälter
288 label_browse: Grasen
289 label_modification: %d änderung
290 label_modification_plural: %d änderungen
291 label_revision: Neuausgabe
292 label_revision_plural: Neuausgaben
293 label_added: hinzugefügt
294 label_modified: geändert
295 label_deleted: gelöscht
296 label_latest_revision: Neueste Neuausgabe
297 label_view_revisions: Die Neuausgaben ansehen
285 298
286 299 button_login: Einloggen
287 300 button_submit: Einreichen
@@ -63,6 +63,7 notice_successful_delete: Successful deletion.
63 63 notice_successful_connection: Successful connection.
64 64 notice_file_not_found: Requested file doesn't exist or has been deleted.
65 65 notice_locking_conflict: Data have been updated by another user.
66 notice_scm_error: Entry and/or revision doesn't exist in the repository.
66 67
67 68 mail_subject_lost_password: Your redMine password
68 69 mail_subject_register: redMine account activation
@@ -136,6 +137,7 field_start_date: Start
136 137 field_done_ratio: %% Done
137 138 field_hide_mail: Hide my email address
138 139 field_comment: Comment
140 field_url: URL
139 141
140 142 label_user: User
141 143 label_user_plural: Users
@@ -282,6 +284,17 label_ago: days ago
282 284 label_contains: contains
283 285 label_not_contains: doesn't contain
284 286 label_day_plural: days
287 label_repository: SVN Repository
288 label_browse: Browse
289 label_modification: %d change
290 label_modification_plural: %d changes
291 label_revision: Revision
292 label_revision_plural: Revisions
293 label_added: added
294 label_modified: modified
295 label_deleted: deleted
296 label_latest_revision: Latest revision
297 label_view_revisions: View revisions
285 298
286 299 button_login: Login
287 300 button_submit: Submit
@@ -63,6 +63,7 notice_successful_delete: Successful deletion.
63 63 notice_successful_connection: Successful connection.
64 64 notice_file_not_found: Requested file doesn't exist or has been deleted.
65 65 notice_locking_conflict: Data have been updated by another user.
66 notice_scm_error: La entrada y/o la revisión no existe en el depósito.
66 67
67 68 mail_subject_lost_password: Tu contraseña del redMine
68 69 mail_subject_register: Activación de la cuenta del redMine
@@ -136,6 +137,7 field_start_date: Comienzo
136 137 field_done_ratio: %% Realizado
137 138 field_hide_mail: Ocultar mi email address
138 139 field_comment: Comentario
140 field_url: URL
139 141
140 142 label_user: Usuario
141 143 label_user_plural: Usuarios
@@ -282,6 +284,17 label_ago: hace
282 284 label_contains: contiene
283 285 label_not_contains: no contiene
284 286 label_day_plural: días
287 label_repository: Depósito SVN
288 label_browse: Hojear
289 label_modification: %d modificación
290 label_modification_plural: %d modificaciones
291 label_revision: Revisión
292 label_revision_plural: Revisiones
293 label_added: agregado
294 label_modified: modificado
295 label_deleted: suprimido
296 label_latest_revision: La revisión más última
297 label_view_revisions: Ver las revisiones
285 298
286 299 button_login: Conexión
287 300 button_submit: Someter
@@ -63,6 +63,7 notice_successful_delete: Suppression effectuée avec succès.
63 63 notice_successful_connection: Connection réussie.
64 64 notice_file_not_found: Le fichier demandé n'existe pas ou a été supprimé.
65 65 notice_locking_conflict: Les données ont été mises à jour par un autre utilisateur. Mise à jour impossible.
66 notice_scm_error: L'entrée et/ou la révision demandée n'existe pas dans le dépôt.
66 67
67 68 mail_subject_lost_password: Votre mot de passe redMine
68 69 mail_subject_register: Activation de votre compte redMine
@@ -137,6 +138,7 field_done_ratio: %% Réalisé
137 138 field_auth_source: Mode d'authentification
138 139 field_hide_mail: Cacher mon adresse mail
139 140 field_comment: Commentaire
141 field_url: URL
140 142
141 143 label_user: Utilisateur
142 144 label_user_plural: Utilisateurs
@@ -283,6 +285,17 label_ago: il y a
283 285 label_contains: contient
284 286 label_not_contains: ne contient pas
285 287 label_day_plural: jours
288 label_repository: Dépôt SVN
289 label_browse: Parcourir
290 label_modification: %d modification
291 label_modification_plural: %d modifications
292 label_revision: Révision
293 label_revision_plural: Révisions
294 label_added: ajouté
295 label_modified: modifié
296 label_deleted: supprimé
297 label_latest_revision: Dernière révision
298 label_view_revisions: Voir les révisions
286 299
287 300 button_login: Connexion
288 301 button_submit: Soumettre
@@ -473,7 +473,7 float: right;
473 473 font-size: 0.8em;
474 474 }
475 475
476 .contextual select {
476 .contextual select, .contextual input {
477 477 font-size: 1em;
478 478 }
479 479
General Comments 0
You need to be logged in to leave comments. Login now