From bc4415850105225b101cdea075b04d44b8700108 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sun, 24 Dec 2006 13:38:45 +0000 Subject: [PATCH] svn browser merged in trunk git-svn-id: http://redmine.rubyforge.org/svn/trunk@106 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/projects_controller.rb | 16 +- app/controllers/repositories_controller.rb | 72 ++++++ app/helpers/application_helper.rb | 4 +- app/helpers/repositories_helper.rb | 19 ++ app/models/permission.rb | 1 + app/models/project.rb | 2 + app/models/repository.rb | 28 +++ app/models/svn_repos.rb | 214 ++++++++++++++++++ app/views/layouts/base.rhtml | 2 + app/views/projects/_form.rhtml | 13 +- app/views/repositories/_dir_list.rhtml | 23 ++ app/views/repositories/_navigation.rhtml | 18 ++ app/views/repositories/browse.rhtml | 11 + app/views/repositories/diff.rhtml | 55 +++++ app/views/repositories/revision.rhtml | 35 +++ app/views/repositories/revisions.rhtml | 38 ++++ app/views/repositories/show.rhtml | 15 ++ config/routes.rb | 3 +- db/migrate/015_create_repositories.rb | 12 + .../016_add_repositories_permissions.rb | 19 ++ doc/CHANGELOG | 1 + lang/de.yml | 13 ++ lang/en.yml | 13 ++ lang/es.yml | 13 ++ lang/fr.yml | 13 ++ public/images/file.png | Bin 0 -> 278 bytes public/images/folder.png | Bin 0 -> 1021 bytes public/stylesheets/application.css | 2 +- public/stylesheets/scm.css | 66 ++++++ 29 files changed, 715 insertions(+), 6 deletions(-) create mode 100644 app/controllers/repositories_controller.rb create mode 100644 app/helpers/repositories_helper.rb create mode 100644 app/models/repository.rb create mode 100644 app/models/svn_repos.rb create mode 100644 app/views/repositories/_dir_list.rhtml create mode 100644 app/views/repositories/_navigation.rhtml create mode 100644 app/views/repositories/browse.rhtml create mode 100644 app/views/repositories/diff.rhtml create mode 100644 app/views/repositories/revision.rhtml create mode 100644 app/views/repositories/revisions.rhtml create mode 100644 app/views/repositories/show.rhtml create mode 100644 db/migrate/015_create_repositories.rb create mode 100644 db/migrate/016_add_repositories_permissions.rb create mode 100644 public/images/file.png create mode 100644 public/images/folder.png create mode 100644 public/stylesheets/scm.css diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b0b00bebf..e4a47d3d1 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -62,6 +62,10 @@ class ProjectsController < ApplicationController @project.custom_fields = CustomField.find(@params[:custom_field_ids]) if @params[:custom_field_ids] @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) } @project.custom_values = @custom_values + if params[:repository_enabled] && params[:repository_enabled] == "1" + @project.repository = Repository.new + @project.repository.attributes = params[:repository] + end if @project.save flash[:notice] = l(:notice_successful_create) redirect_to :controller => 'admin', :action => 'projects' @@ -96,7 +100,17 @@ class ProjectsController < ApplicationController @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) } @project.custom_values = @custom_values end - if @project.update_attributes(params[:project]) + if params[:repository_enabled] + case params[:repository_enabled] + when "0" + @project.repository = nil + when "1" + @project.repository ||= Repository.new + @project.repository.attributes = params[:repository] + end + end + @project.attributes = params[:project] + if @project.save flash[:notice] = l(:notice_successful_update) redirect_to :action => 'settings', :id => @project else diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb new file mode 100644 index 000000000..9dbbfebd9 --- /dev/null +++ b/app/controllers/repositories_controller.rb @@ -0,0 +1,72 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class RepositoriesController < ApplicationController + layout 'base' + before_filter :find_project, :authorize + + def show + @entries = @repository.scm.entries('') + show_error and return unless @entries + @latest_revision = @entries.revisions.latest + end + + def browse + @entries = @repository.scm.entries(@path, @rev) + show_error and return unless @entries + end + + def revisions + @entry = @repository.scm.entry(@path, @rev) + @revisions = @repository.scm.revisions(@path, @rev) + show_error and return unless @entry && @revisions + end + + def entry + if 'raw' == params[:format] + content = @repository.scm.cat(@path, @rev) + show_error and return unless content + send_data content, :filename => @path.split('/').last + end + end + + def revision + @revisions = @repository.scm.revisions '', @rev, @rev, :with_paths => true + show_error and return unless @revisions + @revision = @revisions.first + end + + def diff + @rev_to = params[:rev_to] || (@rev-1) + @diff = @repository.scm.diff(params[:path], @rev, @rev_to) + show_error and return unless @diff + end + +private + def find_project + @project = Project.find(params[:id]) + @repository = @project.repository + @path = params[:path].squeeze('/').gsub(/^\//, '') if params[:path] + @path ||= '' + @rev = params[:rev].to_i if params[:rev] and params[:rev].to_i > 0 + end + + def show_error + flash.now[:notice] = l(:notice_scm_error) + render :nothing => true, :layout => true + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0ca2568ae..29494d707 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -164,7 +164,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder return super if options.delete :no_label label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") label = @template.content_tag("label", label_text, - :class => (@object.errors[field] ? "error" : nil), + :class => (@object && @object.errors[field] ? "error" : nil), :for => (@object_name.to_s + "_" + field.to_s)) label + super end @@ -175,7 +175,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder def select(field, choices, options = {}, html_options = {}) label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") label = @template.content_tag("label", label_text, - :class => (@object.errors[field] ? "error" : nil), + :class => (@object && @object.errors[field] ? "error" : nil), :for => (@object_name.to_s + "_" + field.to_s)) label + super end diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb new file mode 100644 index 000000000..2c7dcdd53 --- /dev/null +++ b/app/helpers/repositories_helper.rb @@ -0,0 +1,19 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module RepositoriesHelper +end diff --git a/app/models/permission.rb b/app/models/permission.rb index b9b61e619..ee4ae56b8 100644 --- a/app/models/permission.rb +++ b/app/models/permission.rb @@ -30,6 +30,7 @@ class Permission < ActiveRecord::Base 1100 => :label_news_plural, 1200 => :label_document_plural, 1300 => :label_attachment_plural, + 1400 => :label_repository }.freeze @@cached_perms_for_public = nil diff --git a/app/models/project.rb b/app/models/project.rb index e8493cb3b..702ccc07c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -25,12 +25,14 @@ class Project < ActiveRecord::Base has_many :documents, :dependent => true has_many :news, :dependent => true, :include => :author has_many :issue_categories, :dependent => true, :order => "issue_categories.name" + has_one :repository, :dependent => true has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => 'custom_fields_projects', :association_foreign_key => 'custom_field_id' acts_as_tree :order => "name", :counter_cache => true validates_presence_of :name, :description validates_uniqueness_of :name validates_associated :custom_values, :on => :update + validates_associated :repository validates_format_of :name, :with => /^[\w\s\'\-]*$/i # returns 5 last created projects diff --git a/app/models/repository.rb b/app/models/repository.rb new file mode 100644 index 000000000..28f2c5a16 --- /dev/null +++ b/app/models/repository.rb @@ -0,0 +1,28 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Repository < ActiveRecord::Base + belongs_to :project + validates_presence_of :url + validates_format_of :url, :with => /^(http|https|svn):\/\/.+/i + + @scm = nil + + def scm + @scm ||= SvnRepos::Base.new url + end +end diff --git a/app/models/svn_repos.rb b/app/models/svn_repos.rb new file mode 100644 index 000000000..55a9f3ea4 --- /dev/null +++ b/app/models/svn_repos.rb @@ -0,0 +1,214 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'rexml/document' + +module SvnRepos + + class CommandFailed < StandardError #:nodoc: + end + + class Base + @url = nil + @login = nil + @password = nil + + def initialize(url, login=nil, password=nil) + @url = url + @login = login if login && !login.empty? + @password = (password || "") if @login + end + + # Returns the entry identified by path and revision identifier + # or nil if entry doesn't exist in the repository + def entry(path=nil, identifier=nil) + e = entries(path, identifier) + e ? e.first : nil + end + + # Returns an Entries collection + # or nil if the given path doesn't exist in the repository + def entries(path=nil, identifier=nil) + path ||= '' + identifier = 'HEAD' unless identifier and identifier > 0 + entries = Entries.new + cmd = "svn list --xml #{target(path)}@#{identifier}" + shellout(cmd) do |io| + begin + doc = REXML::Document.new(io) + doc.elements.each("lists/list/entry") do |entry| + entries << Entry.new({:name => entry.elements['name'].text, + :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text), + :kind => entry.attributes['kind'], + :size => (entry.elements['size'] and entry.elements['size'].text).to_i, + :lastrev => Revision.new({ + :identifier => entry.elements['commit'].attributes['revision'], + :time => Time.parse(entry.elements['commit'].elements['date'].text), + :author => entry.elements['commit'].elements['author'].text + }) + }) + end + rescue + end + end + return nil if $? && $?.exitstatus != 0 + entries.sort_by_name + rescue Errno::ENOENT => e + raise CommandFailed + end + + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + path ||= '' + identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0 + identifier_to = 1 unless identifier_to and identifier_to.to_i > 0 + revisions = Revisions.new + cmd = "svn log --xml -r #{identifier_from}:#{identifier_to} " + cmd << "--verbose " if options[:with_paths] + cmd << target(path) + shellout(cmd) do |io| + begin + doc = REXML::Document.new(io) + doc.elements.each("log/logentry") do |logentry| + paths = [] + logentry.elements.each("paths/path") do |path| + paths << {:action => path.attributes['action'], + :path => path.text + } + end + paths.sort! { |x,y| x[:path] <=> y[:path] } + + revisions << Revision.new({:identifier => logentry.attributes['revision'], + :author => logentry.elements['author'].text, + :time => Time.parse(logentry.elements['date'].text), + :message => logentry.elements['msg'].text, + :paths => paths + }) + end + rescue + end + end + return nil if $? && $?.exitstatus != 0 + revisions + rescue Errno::ENOENT => e + raise CommandFailed + end + + def diff(path, identifier_from, identifier_to=nil) + path ||= '' + if identifier_to and identifier_to.to_i > 0 + identifier_to = identifier_to.to_i + else + identifier_to = identifier_from.to_i - 1 + end + cmd = "svn diff -r " + cmd << "#{identifier_to}:" + cmd << "#{identifier_from}" + cmd << "#{target(path)}@#{identifier_from}" + diff = [] + shellout(cmd) do |io| + io.each_line do |line| + diff << line + end + end + return nil if $? && $?.exitstatus != 0 + diff + rescue Errno::ENOENT => e + raise CommandFailed + end + + def cat(path, identifier=nil) + identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" + cmd = "svn cat #{target(path)}@#{identifier}" + cat = nil + shellout(cmd) do |io| + cat = io.read + end + return nil if $? && $?.exitstatus != 0 + cat + rescue Errno::ENOENT => e + raise CommandFailed + end + + private + def target(path) + " \"" << "#{@url}/#{path}".gsub(/["'?<>\*]/, '') << "\"" + end + + def logger + RAILS_DEFAULT_LOGGER + end + + def shellout(cmd, &block) + logger.debug "Shelling out: #{cmd}" if logger && logger.debug? + IO.popen(cmd) do |io| + block.call(io) if block_given? + end + end + end + + class Entries < Array + def sort_by_name + sort {|x,y| + if x.kind == y.kind + x.name <=> y.name + else + x.kind <=> y.kind + end + } + end + + def revisions + revisions ||= Revisions.new(collect{|entry| entry.lastrev}) + end + end + + class Entry + attr_accessor :name, :path, :kind, :size, :lastrev + def initialize(attributes={}) + self.name = attributes[:name] if attributes[:name] + self.path = attributes[:path] if attributes[:path] + self.kind = attributes[:kind] if attributes[:kind] + self.size = attributes[:size].to_i if attributes[:size] + self.lastrev = attributes[:lastrev] + end + + def is_file? + 'file' == self.kind + end + + def is_dir? + 'dir' == self.kind + end + end + + class Revisions < Array + def latest + sort {|x,y| x.time <=> y.time}.last + end + end + + class Revision + attr_accessor :identifier, :author, :time, :message, :paths + def initialize(attributes={}) + self.identifier = attributes[:identifier] + self.author = attributes[:author] + self.time = attributes[:time] + self.message = attributes[:message] || "" + self.paths = attributes[:paths] + end + end +end \ No newline at end of file diff --git a/app/views/layouts/base.rhtml b/app/views/layouts/base.rhtml index 79dd88cb9..85b550b75 100644 --- a/app/views/layouts/base.rhtml +++ b/app/views/layouts/base.rhtml @@ -91,6 +91,7 @@ <%= link_to l(:label_document_plural), {:controller => 'projects', :action => 'list_documents', :id => @project }, :class => "menuItem" %> <%= link_to l(:label_member_plural), {:controller => 'projects', :action => 'list_members', :id => @project }, :class => "menuItem" %> <%= link_to l(:label_attachment_plural), {:controller => 'projects', :action => 'list_files', :id => @project }, :class => "menuItem" %> + <%= link_to l(:label_repository), {:controller => 'repositories', :action => 'show', :id => @project}, :class => "menuItem" if @project.repository and !@project.repository.new_record? %> <%= link_to_if_authorized l(:label_settings), {:controller => 'projects', :action => 'settings', :id => @project }, :class => "menuItem" %> <% end %> @@ -112,6 +113,7 @@
  • <%= link_to l(:label_document_plural), :controller => 'projects', :action => 'list_documents', :id => @project %>
  • <%= link_to l(:label_member_plural), :controller => 'projects', :action => 'list_members', :id => @project %>
  • <%= link_to l(:label_attachment_plural), :controller => 'projects', :action => 'list_files', :id => @project %>
  • +
  • <%= link_to l(:label_repository), :controller => 'repositories', :action => 'show', :id => @project if @project.repository and !@project.repository.new_record? %>
  • <%= link_to_if_authorized l(:label_settings), :controller => 'projects', :action => 'settings', :id => @project %>
  • <% end %> diff --git a/app/views/projects/_form.rhtml b/app/views/projects/_form.rhtml index ab0b35fab..a6102e012 100644 --- a/app/views/projects/_form.rhtml +++ b/app/views/projects/_form.rhtml @@ -1,4 +1,5 @@ <%= error_messages_for 'project' %> +

    <%= f.text_field :name, :required => true %>

    @@ -22,5 +23,15 @@ <%= custom_field.name %> <% end %>

    <% end %> - + +
    + +

    <%= check_box_tag "repository_enabled", 1, !@project.repository.nil?, :onclick => "Element.toggle('repository');" %> <%= l(:label_repository) %>

    +<%= hidden_field_tag "repository_enabled", 0 %> +
    +<% fields_for :repository, @project.repository, { :builder => TabularFormBuilder, :lang => current_language} do |repository| %> +

    <%= repository.text_field :url, :size => 60, :required => true %>
    (http://, https://, svn://)

    +<% end %> +
    +<%= javascript_tag "Element.hide('repository');" if @project.repository.nil? %>
    diff --git a/app/views/repositories/_dir_list.rhtml b/app/views/repositories/_dir_list.rhtml new file mode 100644 index 000000000..635fba528 --- /dev/null +++ b/app/views/repositories/_dir_list.rhtml @@ -0,0 +1,23 @@ + + + + + + + + + +<% total_size = 0 +@entries.each do |entry| %> + + + + + + + +<% total_size += entry.size +end %> + +
    <%= l(:field_name) %><%= l(:field_filesize) %><%= l(:label_revision) %><%= l(:field_author) %><%= l(:label_date) %>
    <%= 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') %><%= human_size(entry.size) unless entry.is_dir? %><%= link_to entry.lastrev.identifier, :action => 'revision', :id => @project, :rev => entry.lastrev.identifier %><%=h entry.lastrev.author %><%= format_time(entry.lastrev.time) %>
    +

    <%= l(:label_total) %>: <%= human_size(total_size) %>

    \ No newline at end of file diff --git a/app/views/repositories/_navigation.rhtml b/app/views/repositories/_navigation.rhtml new file mode 100644 index 000000000..3ae0f7612 --- /dev/null +++ b/app/views/repositories/_navigation.rhtml @@ -0,0 +1,18 @@ +<%= link_to 'root', :action => 'browse', :id => @project, :path => '', :rev => @rev %> +<% +dirs = path.split('/') +if 'file' == kind + filename = dirs.pop +end +link_path = '' +dirs.each do |dir| + link_path << '/' unless link_path.empty? + link_path << "#{dir}" + %> + / <%= link_to h(dir), :action => 'browse', :id => @project, :path => link_path, :rev => @rev %> +<% end %> +<% if filename %> + / <%= link_to h(filename), :action => 'revisions', :id => @project, :path => "#{link_path}/#{filename}", :rev => @rev %> +<% end %> + +<%= "@ #{revision}" if revision %> \ No newline at end of file diff --git a/app/views/repositories/browse.rhtml b/app/views/repositories/browse.rhtml new file mode 100644 index 000000000..92ad8478b --- /dev/null +++ b/app/views/repositories/browse.rhtml @@ -0,0 +1,11 @@ +<%= stylesheet_link_tag "scm" %> + +
    +<%= start_form_tag %> +<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %> +<%= submit_tag 'OK' %> +
    + +

    <%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %>

    + +<%= render :partial => 'dir_list' %> \ No newline at end of file diff --git a/app/views/repositories/diff.rhtml b/app/views/repositories/diff.rhtml new file mode 100644 index 000000000..d4350cb61 --- /dev/null +++ b/app/views/repositories/diff.rhtml @@ -0,0 +1,55 @@ +

    <%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %>

    + +<%= stylesheet_link_tag "scm" %> + + + + +<% parsing = false +line_num_l = 0 +line_num_r = 0 %> +<% @diff.each do |line| %> +<% + if line =~ /^@@ (\+|\-)(\d+),\d+ (\+|\-)(\d+),\d+ @@/ + line_num_l = $2.to_i + line_num_r = $4.to_i + if parsing %> + + <% end + parsing = true + next + end + next unless parsing +%> + + + +<% case line[0, 1] + when " " %> + + + + + + + + +<% end %> + +
    @<%= @rev %>@<%= @rev_to %>
     
    <%= line_num_l %><%= line_num_r %> +<% line_num_l = line_num_l + 1 + line_num_r = line_num_r + 1 + + when "-" %> +<%= line_num_r %> +<% line_num_r = line_num_r + 1 + + when "+" %> +<%= line_num_l %> +<% line_num_l = line_num_l + 1 + + else + next + end %> + +<%= h(line[1..-1]).gsub(/\s/, " ") %>
    \ No newline at end of file diff --git a/app/views/repositories/revision.rhtml b/app/views/repositories/revision.rhtml new file mode 100644 index 000000000..6a5a20d97 --- /dev/null +++ b/app/views/repositories/revision.rhtml @@ -0,0 +1,35 @@ +<%= stylesheet_link_tag "scm" %> + +
    +<%= start_form_tag %> +<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %> +<%= submit_tag 'OK' %> +
    + +

    <%= l(:label_revision) %> <%= @revision.identifier %>

    + +

    <%= @revision.author %>, <%= format_time(@revision.time) %>

    +<%= simple_format @revision.message %> + +
    +
    <%= l(:label_added) %> 
    +
    <%= l(:label_modified) %> 
    +
    <%= l(:label_deleted) %> 
    +
    + +

    <%= l(:label_attachment_plural) %>

    + + +<% @revision.paths.each do |path| %> + + + + +<% end %> + +
    <%= path[:path] %>
    +<% if path[:action] == "M" %> +<%= link_to 'View diff', :action => 'diff', :id => @project, :path => path[:path].gsub(/^\//, ''), :rev => @revision.identifier %> +<% end %> +
    +

    <%= lwr(:label_modification, @revision.paths.length) %>

    \ No newline at end of file diff --git a/app/views/repositories/revisions.rhtml b/app/views/repositories/revisions.rhtml new file mode 100644 index 000000000..c2e30d30c --- /dev/null +++ b/app/views/repositories/revisions.rhtml @@ -0,0 +1,38 @@ +<%= stylesheet_link_tag "scm" %> + +
    +<%= start_form_tag %> +<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %> +<%= submit_tag 'OK' %> +
    + +

    <%= render :partial => 'navigation', :locals => { :path => @path, :kind => @entry.kind, :revision => @rev } %>

    + +<% if @entry.is_file? %> +

    <%=h @entry.name %>

    +

    <%= link_to 'Download', {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' }, :class => "icon file" %> (<%= human_size @entry.size %>)

    +<% end %> + +

    Revisions

    + + + + + + + + + + +<% @revisions.each do |revision| %> + + + + + + + +<% end %> + +
    #<%= l(:field_author) %><%= l(:label_date) %><%= l(:field_description) %>
    <%= link_to revision.identifier, :action => 'revision', :id => @project, :rev => revision.identifier %><%=h revision.author %><%= format_time(revision.time) %><%= simple_format(h(revision.message)) %><%= link_to 'Diff', :action => 'diff', :id => @project, :path => @path, :rev => revision.identifier if @entry.is_file? && revision != @revisions.last %>
    +

    <%= lwr(:label_modification, @revisions.length) %>

    \ No newline at end of file diff --git a/app/views/repositories/show.rhtml b/app/views/repositories/show.rhtml new file mode 100644 index 000000000..4c95f8844 --- /dev/null +++ b/app/views/repositories/show.rhtml @@ -0,0 +1,15 @@ +<%= stylesheet_link_tag "scm" %> + +

    <%= l(:label_repository) %>

    + +

    <%= l(:label_revision_plural) %>

    +<% if @latest_revision %> +

    <%= l(:label_latest_revision) %>: + <%= link_to @latest_revision.identifier, :action => 'revision', :id => @project, :rev => @latest_revision.identifier %>
    + <%= @latest_revision.author %>, <%= format_time(@latest_revision.time) %>

    +<% end %> +

    <%= link_to l(:label_view_revisions), :action => 'revisions', :id => @project %>

    + + +

    <%= l(:label_browse) %>

    +<%= render :partial => 'dir_list' %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 2559159f1..0871cec79 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,7 +9,8 @@ ActionController::Routing::Routes.draw do |map| # You can have the root of your site routed by hooking up '' # -- just remember to delete public/index.html. map.connect '', :controller => "welcome" - + + map.connect 'repositories/:action/:id/:path', :controller => 'repositories' map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow' map.connect 'help/:ctrl/:page', :controller => 'help' map.connect ':controller/:action/:id/:sort_key/:sort_order' diff --git a/db/migrate/015_create_repositories.rb b/db/migrate/015_create_repositories.rb new file mode 100644 index 000000000..d8c0524b3 --- /dev/null +++ b/db/migrate/015_create_repositories.rb @@ -0,0 +1,12 @@ +class CreateRepositories < ActiveRecord::Migration + def self.up + create_table :repositories, :force => true do |t| + t.column "project_id", :integer, :default => 0, :null => false + t.column "url", :string, :default => "", :null => false + end + end + + def self.down + drop_table :repositories + end +end diff --git a/db/migrate/016_add_repositories_permissions.rb b/db/migrate/016_add_repositories_permissions.rb new file mode 100644 index 000000000..992f8dccd --- /dev/null +++ b/db/migrate/016_add_repositories_permissions.rb @@ -0,0 +1,19 @@ +class AddRepositoriesPermissions < ActiveRecord::Migration + def self.up + Permission.create :controller => "repositories", :action => "show", :description => "button_view", :sort => 1450, :is_public => true + Permission.create :controller => "repositories", :action => "browse", :description => "label_browse", :sort => 1460, :is_public => true + Permission.create :controller => "repositories", :action => "entry", :description => "entry", :sort => 1462, :is_public => true + Permission.create :controller => "repositories", :action => "revisions", :description => "label_view_revisions", :sort => 1470, :is_public => true + Permission.create :controller => "repositories", :action => "revision", :description => "label_view_revisions", :sort => 1472, :is_public => true + Permission.create :controller => "repositories", :action => "diff", :description => "diff", :sort => 1480, :is_public => true + end + + def self.down + Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'show']).destroy + Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'browse']).destroy + Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'entry']).destroy + Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'revisions']).destroy + Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'revision']).destroy + Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'diff']).destroy + end +end diff --git a/doc/CHANGELOG b/doc/CHANGELOG index 4c4ba17c7..36bac591b 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -7,6 +7,7 @@ http://redmine.org/ == xx/xx/2006 v0.x.x +* simple SVN browser added (just needs svn binaries in PATH) * comments can now be added on news * "my page" is now customizable * more powerfull and savable filters for issues lists diff --git a/lang/de.yml b/lang/de.yml index aa091961b..34fedc1ea 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -63,6 +63,7 @@ notice_successful_delete: Erfolgreiche Auslassung. notice_successful_connection: Erfolgreicher Anschluß. notice_file_not_found: Erbetene Akte besteht nicht oder ist gelöscht worden. notice_locking_conflict: Data have been updated by another user. +notice_scm_error: Eintragung und/oder Neuausgabe besteht nicht im Behälter. mail_subject_lost_password: Dein redMine Kennwort mail_subject_register: redMine Kontoaktivierung @@ -136,6 +137,7 @@ field_start_date: Beginn field_done_ratio: %% Getan field_hide_mail: Mein email address verstecken field_comment: Anmerkung +field_url: URL label_user: Benutzer label_user_plural: Benutzer @@ -282,6 +284,17 @@ label_ago: vor label_contains: enthält label_not_contains: enthält nicht label_day_plural: Tage +label_repository: SVN Behälter +label_browse: Grasen +label_modification: %d änderung +label_modification_plural: %d änderungen +label_revision: Neuausgabe +label_revision_plural: Neuausgaben +label_added: hinzugefügt +label_modified: geändert +label_deleted: gelöscht +label_latest_revision: Neueste Neuausgabe +label_view_revisions: Die Neuausgaben ansehen button_login: Einloggen button_submit: Einreichen diff --git a/lang/en.yml b/lang/en.yml index 8c2de4bc3..b6734985a 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -63,6 +63,7 @@ notice_successful_delete: Successful deletion. notice_successful_connection: Successful connection. notice_file_not_found: Requested file doesn't exist or has been deleted. notice_locking_conflict: Data have been updated by another user. +notice_scm_error: Entry and/or revision doesn't exist in the repository. mail_subject_lost_password: Your redMine password mail_subject_register: redMine account activation @@ -136,6 +137,7 @@ field_start_date: Start field_done_ratio: %% Done field_hide_mail: Hide my email address field_comment: Comment +field_url: URL label_user: User label_user_plural: Users @@ -282,6 +284,17 @@ label_ago: days ago label_contains: contains label_not_contains: doesn't contain label_day_plural: days +label_repository: SVN Repository +label_browse: Browse +label_modification: %d change +label_modification_plural: %d changes +label_revision: Revision +label_revision_plural: Revisions +label_added: added +label_modified: modified +label_deleted: deleted +label_latest_revision: Latest revision +label_view_revisions: View revisions button_login: Login button_submit: Submit diff --git a/lang/es.yml b/lang/es.yml index ce19ef658..7eb3fa9fd 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -63,6 +63,7 @@ notice_successful_delete: Successful deletion. notice_successful_connection: Successful connection. notice_file_not_found: Requested file doesn't exist or has been deleted. notice_locking_conflict: Data have been updated by another user. +notice_scm_error: La entrada y/o la revisión no existe en el depósito. mail_subject_lost_password: Tu contraseña del redMine mail_subject_register: Activación de la cuenta del redMine @@ -136,6 +137,7 @@ field_start_date: Comienzo field_done_ratio: %% Realizado field_hide_mail: Ocultar mi email address field_comment: Comentario +field_url: URL label_user: Usuario label_user_plural: Usuarios @@ -282,6 +284,17 @@ label_ago: hace label_contains: contiene label_not_contains: no contiene label_day_plural: días +label_repository: Depósito SVN +label_browse: Hojear +label_modification: %d modificación +label_modification_plural: %d modificaciones +label_revision: Revisión +label_revision_plural: Revisiones +label_added: agregado +label_modified: modificado +label_deleted: suprimido +label_latest_revision: La revisión más última +label_view_revisions: Ver las revisiones button_login: Conexión button_submit: Someter diff --git a/lang/fr.yml b/lang/fr.yml index a351a5a92..4b4b04925 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -63,6 +63,7 @@ notice_successful_delete: Suppression effectuée avec succès. notice_successful_connection: Connection réussie. notice_file_not_found: Le fichier demandé n'existe pas ou a été supprimé. notice_locking_conflict: Les données ont été mises à jour par un autre utilisateur. Mise à jour impossible. +notice_scm_error: L'entrée et/ou la révision demandée n'existe pas dans le dépôt. mail_subject_lost_password: Votre mot de passe redMine mail_subject_register: Activation de votre compte redMine @@ -137,6 +138,7 @@ field_done_ratio: %% Réalisé field_auth_source: Mode d'authentification field_hide_mail: Cacher mon adresse mail field_comment: Commentaire +field_url: URL label_user: Utilisateur label_user_plural: Utilisateurs @@ -283,6 +285,17 @@ label_ago: il y a label_contains: contient label_not_contains: ne contient pas label_day_plural: jours +label_repository: Dépôt SVN +label_browse: Parcourir +label_modification: %d modification +label_modification_plural: %d modifications +label_revision: Révision +label_revision_plural: Révisions +label_added: ajouté +label_modified: modifié +label_deleted: supprimé +label_latest_revision: Dernière révision +label_view_revisions: Voir les révisions button_login: Connexion button_submit: Soumettre diff --git a/public/images/file.png b/public/images/file.png new file mode 100644 index 0000000000000000000000000000000000000000..7a087194175eeafd61f542606887e2f20ef911d3 GIT binary patch literal 278 zcmeAS@N?(olHy`uVBq!ia0vp^d?3ui3?$#C89V|~>?NMQuI$%%#QCknZms#f04T&+ z;1OBOz`*qZgc+UIn9KkQvZOouIx;Y9?C1WI$O_~e1o(uw{%2tL{{8#=_wS!Qdv@>M zz3bPnpE+~p$dMxl4jh;{b7o6ROGQORadGkg|3HNx5HtJ#MFVdQ&MBb@0MGMr5dZ)H literal 0 HcmV?d00001 diff --git a/public/images/folder.png b/public/images/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..32175ebd405a978972d765aa7e404918f2ac5eb5 GIT binary patch literal 1021 zcmeAS@N?(olHy`uVBq!ia0vp^{2(?5GmvyVaHJSWv6p!Iy0Ty65$Cs3?=Jd13n;`{ z;1LOw;`#u>j81DzW&j0Q(j9#r85lP9bN@+XWnf@t2=EDU{m;OV7^l|NXgG7K&Gebm zHZOJBzrpMHCcm>A{WovjbaiL!t^HZ|j<-BI+yCUkj7JydUA}th>D86bZ*IPQ_tvZX z`<_2N@$TuVr_Z0fdVS^X%Ud7c-uv|N#mCPdK7al6{oC6=|9<FMj`;nOOexv`Lcl$v@NYsV(KvYSUz03koGqS3j3^ HP6