summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2007-06-12 20:12:05 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2007-06-12 20:12:05 +0000
commit438161ad1fd37aadbfa3f5a875540730fd6d70c3 (patch)
treeb396b2379835a6f768023cdc0f7042259f560875 /app
parent4dddb606a6d24c7f95e52f08e689464ab6f8b5b8 (diff)
downloadredmine-438161ad1fd37aadbfa3f5a875540730fd6d70c3.tar.gz
redmine-438161ad1fd37aadbfa3f5a875540730fd6d70c3.zip
Added basic support for CVS and Mercurial SCMs.
Browsing, changesets fetching and diff viewing are implemented. Only tested with local repositories. Thanks to Ralph Vater for CVS specific code. git-svn-id: http://redmine.rubyforge.org/svn/trunk@559 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app')
-rw-r--r--app/controllers/projects_controller.rb8
-rw-r--r--app/controllers/repositories_controller.rb67
-rw-r--r--app/helpers/application_helper.rb4
-rw-r--r--app/helpers/repositories_helper.rb35
-rw-r--r--app/models/repository.rb78
-rw-r--r--app/models/repository/cvs.rb150
-rw-r--r--app/models/repository/mercurial.rb81
-rw-r--r--app/models/repository/subversion.rb69
-rw-r--r--app/models/svn_repos.rb436
-rw-r--r--app/views/projects/_form.rhtml20
-rw-r--r--app/views/projects/_repository.rhtml3
-rw-r--r--app/views/repositories/_dir_list.rhtml14
-rw-r--r--app/views/repositories/_navigation.rhtml5
-rw-r--r--app/views/repositories/_revisions.rhtml9
-rw-r--r--app/views/repositories/changes.rhtml13
-rw-r--r--app/views/repositories/revision.rhtml6
-rw-r--r--app/views/repositories/revisions.rhtml18
-rw-r--r--app/views/repositories/show.rhtml8
18 files changed, 462 insertions, 562 deletions
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 0dc6cba26..ead1a224a 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -35,6 +35,8 @@ class ProjectsController < ApplicationController
helper IssuesHelper
helper :queries
include QueriesHelper
+ helper :repositories
+ include RepositoriesHelper
def index
list
@@ -70,7 +72,7 @@ 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
if params[:repository_enabled] && params[:repository_enabled] == "1"
- @project.repository = Repository.new
+ @project.repository = Repository.factory(params[:repository_scm])
@project.repository.attributes = params[:repository]
end
if "1" == params[:wiki_enabled]
@@ -116,8 +118,8 @@ class ProjectsController < ApplicationController
when "0"
@project.repository = nil
when "1"
- @project.repository ||= Repository.new
- @project.repository.update_attributes params[:repository]
+ @project.repository ||= Repository.factory(params[:repository_scm])
+ @project.repository.update_attributes params[:repository] if @project.repository
end
end
if params[:wiki_enabled]
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index 4252e58be..21e1997ab 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -21,42 +21,42 @@ require 'digest/sha1'
class RepositoriesController < ApplicationController
layout 'base'
- before_filter :find_project
- before_filter :authorize, :except => [:stats, :graph]
+ before_filter :find_project, :except => [:update_form]
+ before_filter :authorize, :except => [:update_form, :stats, :graph]
before_filter :check_project_privacy, :only => [:stats, :graph]
def show
+ # check if new revisions have been committed in the repository
+ @repository.fetch_changesets if Setting.autofetch_changesets?
# get entries for the browse frame
- @entries = @repository.scm.entries('')
+ @entries = @repository.entries('')
show_error and return unless @entries
- # check if new revisions have been committed in the repository
- scm_latestrev = @entries.revisions.latest
- if Setting.autofetch_changesets? && scm_latestrev && ((@repository.latest_changeset.nil?) || (@repository.latest_changeset.revision < scm_latestrev.identifier.to_i))
- @repository.fetch_changesets
- @repository.reload
- end
- @changesets = @repository.changesets.find(:all, :limit => 5, :order => "committed_on DESC")
+ # latest changesets
+ @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
end
def browse
- @entries = @repository.scm.entries(@path, @rev)
- show_error and return unless @entries
+ @entries = @repository.entries(@path, @rev)
+ show_error and return unless @entries
+ end
+
+ def changes
+ @entry = @repository.scm.entry(@path, @rev)
+ show_error and return unless @entry
+ @changes = Change.find(:all, :include => :changeset,
+ :conditions => ["repository_id = ? AND path = ?", @repository.id, @path.with_leading_slash],
+ :order => "committed_on DESC")
end
def revisions
- unless @path == ''
- @entry = @repository.scm.entry(@path, @rev)
- show_error and return unless @entry
- end
- @repository.changesets_with_path @path do
- @changeset_count = @repository.changesets.count(:select => "DISTINCT #{Changeset.table_name}.id")
- @changeset_pages = Paginator.new self, @changeset_count,
- 25,
- params['page']
- @changesets = @repository.changesets.find(:all,
- :limit => @changeset_pages.items_per_page,
- :offset => @changeset_pages.current.offset)
- end
+ @changeset_count = @repository.changesets.count
+ @changeset_pages = Paginator.new self, @changeset_count,
+ 25,
+ params['page']
+ @changesets = @repository.changesets.find(:all,
+ :limit => @changeset_pages.items_per_page,
+ :offset => @changeset_pages.current.offset)
+
render :action => "revisions", :layout => false if request.xhr?
end
@@ -81,12 +81,12 @@ class RepositoriesController < ApplicationController
end
def diff
- @rev_to = (params[:rev_to] && params[:rev_to].to_i > 0) ? params[:rev_to].to_i : (@rev - 1)
+ @rev_to = params[:rev_to] ? params[:rev_to].to_i : (@rev - 1)
@diff_type = ('sbs' == params[:type]) ? 'sbs' : 'inline'
@cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
unless read_fragment(@cache_key)
- @diff = @repository.scm.diff(@path, @rev, @rev_to, type)
+ @diff = @repository.diff(@path, @rev, @rev_to, type)
show_error and return unless @diff
end
end
@@ -110,6 +110,11 @@ class RepositoriesController < ApplicationController
end
end
+ def update_form
+ @repository = Repository.factory(params[:repository_scm])
+ render :partial => 'projects/repository', :locals => {:repository => @repository}
+ end
+
private
def find_project
@project = Project.find(params[:id])
@@ -117,7 +122,7 @@ private
render_404 and return false unless @repository
@path = params[:path].squeeze('/') if params[:path]
@path ||= ''
- @rev = params[:rev].to_i if params[:rev] and params[:rev].to_i > 0
+ @rev = params[:rev].to_i if params[:rev]
rescue ActiveRecord::RecordNotFound
render_404
end
@@ -218,3 +223,9 @@ class Date
(date.year - self.year)*52 + (date.cweek - self.cweek)
end
end
+
+class String
+ def with_leading_slash
+ starts_with?('/') ? self : "/#{self}"
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 12302a073..564a9938f 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -251,7 +251,9 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
src = <<-END_SRC
def #{selector}(field, options = {})
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_text = l(options[:label]) if options[:label]
+ label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
+ label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
label = @template.content_tag("label", label_text,
:class => (@object && @object.errors[field] ? "error" : nil),
:for => (@object_name.to_s + "_" + field.to_s))
diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb
index 2c7dcdd53..e2058a712 100644
--- a/app/helpers/repositories_helper.rb
+++ b/app/helpers/repositories_helper.rb
@@ -16,4 +16,39 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module RepositoriesHelper
+ def repository_field_tags(form, repository)
+ method = repository.class.name.demodulize.underscore + "_field_tags"
+ send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method)
+ end
+
+ def scm_select_tag
+ container = [[]]
+ REDMINE_SUPPORTED_SCM.each {|scm| container << ["Repository::#{scm}".constantize.scm_name, scm]}
+ select_tag('repository_scm',
+ options_for_select(container, @project.repository.class.name.demodulize),
+ :disabled => (@project.repository && !@project.repository.new_record?),
+ :onchange => remote_function(:update => "repository_fields", :url => { :controller => 'repositories', :action => 'update_form', :id => @project }, :with => "Form.serialize(this.form)")
+ )
+ end
+
+ def with_leading_slash(path)
+ path ||= ''
+ path.starts_with?("/") ? "/#{path}" : path
+ end
+
+ def subversion_field_tags(form, repository)
+ content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
+ '<br />(http://, https://, svn://, file:///)') +
+ content_tag('p', form.text_field(:login, :size => 30)) +
+ content_tag('p', form.password_field(:password, :size => 30))
+ end
+
+ def mercurial_field_tags(form, repository)
+ content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
+ end
+
+ def cvs_field_tags(form, repository)
+ content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
+ content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 692c446d6..667ef5efc 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -17,70 +17,31 @@
class Repository < ActiveRecord::Base
belongs_to :project
- has_many :changesets, :dependent => :destroy, :order => 'revision DESC'
+ has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.revision DESC"
has_many :changes, :through => :changesets
- has_one :latest_changeset, :class_name => 'Changeset', :foreign_key => :repository_id, :order => 'revision DESC'
-
- attr_protected :root_url
-
- validates_presence_of :url
- validates_format_of :url, :with => /^(http|https|svn|file):\/\/.+/i
def scm
- @scm ||= SvnRepos::Base.new url, root_url, login, password
+ @scm ||= self.scm_adapter.new url, root_url, login, password
update_attribute(:root_url, @scm.root_url) if root_url.blank?
@scm
end
- def url=(str)
- super if root_url.blank?
+ def scm_name
+ self.class.scm_name
end
- def changesets_with_path(path="")
- path = "/#{path}%"
- path = url.gsub(/^#{root_url}/, '') + path if root_url && root_url != url
- path.squeeze!("/")
- # Custom select and joins is done to allow conditions on changes table without loading associated Change objects
- # Required for changesets with a great number of changes (eg. 100,000)
- Changeset.with_scope(:find => { :select => "DISTINCT #{Changeset.table_name}.*", :joins => "LEFT OUTER JOIN #{Change.table_name} ON #{Change.table_name}.changeset_id = #{Changeset.table_name}.id", :conditions => ["#{Change.table_name}.path LIKE ?", path] }) do
- yield
- end
+ def entries(path=nil, identifier=nil)
+ scm.entries(path, identifier)
end
- def fetch_changesets
- scm_info = scm.info
- if scm_info
- lastrev_identifier = scm_info.lastrev.identifier.to_i
- if latest_changeset.nil? || latest_changeset.revision < lastrev_identifier
- logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
- identifier_from = latest_changeset ? latest_changeset.revision + 1 : 1
- while (identifier_from <= lastrev_identifier)
- # loads changesets by batches of 200
- identifier_to = [identifier_from + 199, lastrev_identifier].min
- revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
- transaction do
- revisions.reverse_each do |revision|
- changeset = Changeset.create(:repository => self,
- :revision => revision.identifier,
- :committer => revision.author,
- :committed_on => revision.time,
- :comments => revision.message)
-
- revision.paths.each do |change|
- Change.create(:changeset => changeset,
- :action => change[:action],
- :path => change[:path],
- :from_path => change[:from_path],
- :from_revision => change[:from_revision])
- end
- end
- end unless revisions.nil?
- identifier_from = identifier_to + 1
- end
- end
- end
+ def diff(path, rev, rev_to, type)
+ scm.diff(path, rev, rev_to, type)
end
+ def latest_changeset
+ @latest_changeset ||= changesets.find(:first)
+ end
+
def scan_changesets_for_issue_ids
self.changesets.each(&:scan_comment_for_issue_ids)
end
@@ -96,4 +57,19 @@ class Repository < ActiveRecord::Base
def self.scan_changesets_for_issue_ids
find(:all).each(&:scan_changesets_for_issue_ids)
end
+
+ def self.scm_name
+ 'Abstract'
+ end
+
+ def self.available_scm
+ subclasses.collect {|klass| [klass.scm_name, klass.name]}
+ end
+
+ def self.factory(klass_name, *args)
+ klass = "Repository::#{klass_name}".constantize
+ klass.new(*args)
+ rescue
+ nil
+ end
end
diff --git a/app/models/repository/cvs.rb b/app/models/repository/cvs.rb
new file mode 100644
index 000000000..d6d4ed317
--- /dev/null
+++ b/app/models/repository/cvs.rb
@@ -0,0 +1,150 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 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 'redmine/scm/adapters/cvs_adapter'
+require 'digest/sha1'
+
+class Repository::Cvs < Repository
+ validates_presence_of :url, :root_url
+
+ def scm_adapter
+ Redmine::Scm::Adapters::CvsAdapter
+ end
+
+ def self.scm_name
+ 'CVS'
+ end
+
+ def entry(path, identifier)
+ e = entries(path, identifier)
+ e ? e.first : nil
+ end
+
+ def entries(path=nil, identifier=nil)
+ entries=scm.entries(path, identifier)
+ if entries
+ entries.each() do |entry|
+ unless entry.lastrev.nil? || entry.lastrev.identifier
+ change=changes.find_by_revision_and_path( entry.lastrev.revision, scm.with_leading_slash(entry.path) )
+ if change
+ entry.lastrev.identifier=change.changeset.revision
+ entry.lastrev.author=change.changeset.committer
+ entry.lastrev.revision=change.revision
+ entry.lastrev.branch=change.branch
+ end
+ end
+ end
+ end
+ entries
+ end
+
+ def diff(path, rev, rev_to, type)
+ #convert rev to revision. CVS can't handle changesets here
+ diff=[]
+ changeset_from=changesets.find_by_revision(rev)
+ if rev_to.to_i > 0
+ changeset_to=changesets.find_by_revision(rev_to)
+ end
+ changeset_from.changes.each() do |change_from|
+
+ revision_from=nil
+ revision_to=nil
+
+ revision_from=change_from.revision if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
+
+ if revision_from
+ if changeset_to
+ changeset_to.changes.each() do |change_to|
+ revision_to=change_to.revision if change_to.path==change_from.path
+ end
+ end
+ unless revision_to
+ revision_to=scm.get_previous_revision(revision_from)
+ end
+ diff=diff+scm.diff(change_from.path, revision_from, revision_to, type)
+ end
+ end
+ return diff
+ end
+
+ def fetch_changesets
+ #not the preferred way with CVS. maybe we should introduce always a cron-job for this
+ last_commit = changesets.maximum(:committed_on)
+
+ # some nifty bits to introduce a commit-id with cvs
+ # natively cvs doesn't provide any kind of changesets, there is only a revision per file.
+ # we now take a guess using the author, the commitlog and the commit-date.
+
+ # last one is the next step to take. the commit-date is not equal for all
+ # commits in one changeset. cvs update the commit-date when the *,v file was touched. so
+ # we use a small delta here, to merge all changes belonging to _one_ changeset
+ time_delta=10.seconds
+
+ transaction do
+ scm.revisions('', last_commit, nil, :with_paths => true) do |revision|
+ # only add the change to the database, if it doen't exists. the cvs log
+ # is not exclusive at all.
+ unless changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
+ revision
+ cs=Changeset.find(:first, :conditions=>{
+ :committed_on=>revision.time-time_delta..revision.time+time_delta,
+ :committer=>revision.author,
+ :comments=>revision.message
+ })
+
+ # create a new changeset....
+ unless cs
+ # we use a negative changeset-number here (just for inserting)
+ # later on, we calculate a continous positive number
+ next_rev = changesets.minimum(:revision)
+ next_rev = 0 if next_rev.nil? or next_rev > 0
+ next_rev = next_rev - 1
+
+ cs=Changeset.create(:repository => self,
+ :revision => next_rev,
+ :committer => revision.author,
+ :committed_on => revision.time,
+ :comments => revision.message)
+ end
+
+ #convert CVS-File-States to internal Action-abbrevations
+ #default action is (M)odified
+ action="M"
+ if revision.paths[0][:action]=="Exp" && revision.paths[0][:revision]=="1.1"
+ action="A" #add-action always at first revision (= 1.1)
+ elsif revision.paths[0][:action]=="dead"
+ action="D" #dead-state is similar to Delete
+ end
+
+ Change.create(:changeset => cs,
+ :action => action,
+ :path => scm.with_leading_slash(revision.paths[0][:path]),
+ :revision => revision.paths[0][:revision],
+ :branch => revision.paths[0][:branch]
+ )
+ end
+ end
+
+ next_rev = [changesets.maximum(:revision) || 0, 0].max
+ changesets.find(:all, :conditions=>["revision < 0"], :order=>"committed_on ASC").each() do |changeset|
+ next_rev = next_rev + 1
+ changeset.revision = next_rev
+ changeset.save!
+ end
+ end
+ end
+end
diff --git a/app/models/repository/mercurial.rb b/app/models/repository/mercurial.rb
new file mode 100644
index 000000000..5d9ea9cd4
--- /dev/null
+++ b/app/models/repository/mercurial.rb
@@ -0,0 +1,81 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 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 'redmine/scm/adapters/mercurial_adapter'
+
+class Repository::Mercurial < Repository
+ attr_protected :root_url
+ validates_presence_of :url
+
+ def scm_adapter
+ Redmine::Scm::Adapters::MercurialAdapter
+ end
+
+ def self.scm_name
+ 'Mercurial'
+ end
+
+ def entries(path=nil, identifier=nil)
+ entries=scm.entries(path, identifier)
+ if entries
+ entries.each do |entry|
+ next unless entry.is_file?
+ # Search the DB for the entry's last change
+ change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
+ if change
+ entry.lastrev.identifier = change.changeset.revision
+ entry.lastrev.name = change.changeset.revision
+ entry.lastrev.author = change.changeset.committer
+ entry.lastrev.revision = change.revision
+ end
+ end
+ end
+ entries
+ end
+
+ def fetch_changesets
+ scm_info = scm.info
+ if scm_info
+ # latest revision found in database
+ db_revision = latest_changeset ? latest_changeset.revision : nil
+ # latest revision in the repository
+ scm_revision = scm_info.lastrev.identifier.to_i
+
+ unless changesets.find_by_revision(scm_revision)
+ revisions = scm.revisions('', db_revision, nil)
+ transaction do
+ revisions.reverse_each do |revision|
+ changeset = Changeset.create(:repository => self,
+ :revision => revision.identifier,
+ :scmid => revision.scmid,
+ :committer => revision.author,
+ :committed_on => revision.time,
+ :comments => revision.message)
+
+ revision.paths.each do |change|
+ Change.create(:changeset => changeset,
+ :action => change[:action],
+ :path => change[:path],
+ :from_path => change[:from_path],
+ :from_revision => change[:from_revision])
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/repository/subversion.rb b/app/models/repository/subversion.rb
new file mode 100644
index 000000000..cc9c975a3
--- /dev/null
+++ b/app/models/repository/subversion.rb
@@ -0,0 +1,69 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 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 'redmine/scm/adapters/subversion_adapter'
+
+class Repository::Subversion < Repository
+ attr_protected :root_url
+ validates_presence_of :url
+ validates_format_of :url, :with => /^(http|https|svn|file):\/\/.+/i
+
+ def scm_adapter
+ Redmine::Scm::Adapters::SubversionAdapter
+ end
+
+ def self.scm_name
+ 'Subversion'
+ end
+
+ def fetch_changesets
+ scm_info = scm.info
+ if scm_info
+ # latest revision found in database
+ db_revision = latest_changeset ? latest_changeset.revision : 0
+ # latest revision in the repository
+ scm_revision = scm_info.lastrev.identifier.to_i
+ if db_revision < scm_revision
+ logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
+ identifier_from = db_revision + 1
+ while (identifier_from <= scm_revision)
+ # loads changesets by batches of 200
+ identifier_to = [identifier_from + 199, scm_revision].min
+ revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
+ transaction do
+ revisions.reverse_each do |revision|
+ changeset = Changeset.create(:repository => self,
+ :revision => revision.identifier,
+ :committer => revision.author,
+ :committed_on => revision.time,
+ :comments => revision.message)
+
+ revision.paths.each do |change|
+ Change.create(:changeset => changeset,
+ :action => change[:action],
+ :path => change[:path],
+ :from_path => change[:from_path],
+ :from_revision => change[:from_revision])
+ end
+ end
+ end unless revisions.nil?
+ identifier_from = identifier_to + 1
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/svn_repos.rb b/app/models/svn_repos.rb
deleted file mode 100644
index 45d0b289a..000000000
--- a/app/models/svn_repos.rb
+++ /dev/null
@@ -1,436 +0,0 @@
-# redMine - project management software
-# Copyright (C) 2006-2007 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'
-require 'cgi'
-
-module SvnRepos
-
- class CommandFailed < StandardError #:nodoc:
- end
-
- class Base
-
- def initialize(url, root_url=nil, login=nil, password=nil)
- @url = url
- @login = login if login && !login.empty?
- @password = (password || "") if @login
- @root_url = root_url.blank? ? retrieve_root_url : root_url
- end
-
- def root_url
- @root_url
- end
-
- def url
- @url
- end
-
- # get info about the svn repository
- def info
- cmd = "svn info --xml #{target('')}"
- cmd << " --username #{@login} --password #{@password}" if @login
- info = nil
- shellout(cmd) do |io|
- begin
- doc = REXML::Document.new(io)
- #root_url = doc.elements["info/entry/repository/root"].text
- info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text,
- :lastrev => Revision.new({
- :identifier => doc.elements["info/entry/commit"].attributes['revision'],
- :time => Time.parse(doc.elements["info/entry/commit/date"].text),
- :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "")
- })
- })
- rescue
- end
- end
- return nil if $? && $?.exitstatus != 0
- info
- rescue Errno::ENOENT => e
- return nil
- 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}"
- cmd << " --username #{@login} --password #{@password}" if @login
- 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'] ? 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 << " --username #{@login} --password #{@password}" if @login
- 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,
- :from_path => path.attributes['copyfrom-path'],
- :from_revision => path.attributes['copyfrom-rev']
- }
- end
- paths.sort! { |x,y| x[:path] <=> y[:path] }
-
- revisions << Revision.new({:identifier => logentry.attributes['revision'],
- :author => (logentry.elements['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, type="inline")
- 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}"
- cmd << " --username #{@login} --password #{@password}" if @login
- diff = []
- shellout(cmd) do |io|
- io.each_line do |line|
- diff << line
- end
- end
- return nil if $? && $?.exitstatus != 0
- DiffTableList.new diff, type
-
- 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}"
- cmd << " --username #{@login} --password #{@password}" if @login
- cat = nil
- shellout(cmd) do |io|
- io.binmode
- cat = io.read
- end
- return nil if $? && $?.exitstatus != 0
- cat
- rescue Errno::ENOENT => e
- raise CommandFailed
- end
-
- private
- def retrieve_root_url
- info = self.info
- info ? info.root_url : nil
- end
-
- def target(path)
- path ||= ""
- base = path.match(/^\//) ? root_url : url
- " \"" << "#{base}/#{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, "r+") do |io|
- io.close_write
- 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 Info
- attr_accessor :root_url, :lastrev
- def initialize(attributes={})
- self.root_url = attributes[:root_url] if attributes[:root_url]
- self.lastrev = attributes[: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
-
- def is_text?
- Redmine::MimeType.is_type?('text', name)
- 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
-
- # A line of Diff
- class Diff
-
- attr_accessor :nb_line_left
- attr_accessor :line_left
- attr_accessor :nb_line_right
- attr_accessor :line_right
- attr_accessor :type_diff_right
- attr_accessor :type_diff_left
-
- def initialize ()
- self.nb_line_left = ''
- self.nb_line_right = ''
- self.line_left = ''
- self.line_right = ''
- self.type_diff_right = ''
- self.type_diff_left = ''
- end
-
- def inspect
- puts '### Start Line Diff ###'
- puts self.nb_line_left
- puts self.line_left
- puts self.nb_line_right
- puts self.line_right
- end
- end
-
- class DiffTableList < Array
-
- def initialize (diff, type="inline")
- diff_table = DiffTable.new type
- diff.each do |line|
- if line =~ /^Index: (.*)$/
- self << diff_table if diff_table.length > 1
- diff_table = DiffTable.new type
- end
- a = diff_table.add_line line
- end
- self << diff_table
- end
- end
-
- # Class for create a Diff
- class DiffTable < Hash
-
- attr_reader :file_name, :line_num_l, :line_num_r
-
- # Initialize with a Diff file and the type of Diff View
- # The type view must be inline or sbs (side_by_side)
- def initialize (type="inline")
- @parsing = false
- @nb_line = 1
- @start = false
- @before = 'same'
- @second = true
- @type = type
- end
-
- # Function for add a line of this Diff
- def add_line(line)
- unless @parsing
- if line =~ /^Index: (.*)$/
- @file_name = $1
- return false
- elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
- @line_num_l = $2.to_i
- @line_num_r = $5.to_i
- @parsing = true
- end
- else
- if line =~ /^_+$/
- self.delete(self.keys.sort.last)
- @parsing = false
- return false
- elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
- @line_num_l = $2.to_i
- @line_num_r = $5.to_i
- else
- @nb_line += 1 if parse_line(line, @type)
- end
- end
- return true
- end
-
- def inspect
- puts '### DIFF TABLE ###'
- puts "file : #{file_name}"
- self.each do |d|
- d.inspect
- end
- end
-
- private
-
- # Test if is a Side By Side type
- def sbs?(type, func)
- if @start and type == "sbs"
- if @before == func and @second
- tmp_nb_line = @nb_line
- self[tmp_nb_line] = Diff.new
- else
- @second = false
- tmp_nb_line = @start
- @start += 1
- @nb_line -= 1
- end
- else
- tmp_nb_line = @nb_line
- @start = @nb_line
- self[tmp_nb_line] = Diff.new
- @second = true
- end
- unless self[tmp_nb_line]
- @nb_line += 1
- self[tmp_nb_line] = Diff.new
- else
- self[tmp_nb_line]
- end
- end
-
- # Escape the HTML for the diff
- def escapeHTML(line)
- CGI.escapeHTML(line).gsub(/\s/, '&nbsp;')
- end
-
- def parse_line (line, type="inline")
- if line[0, 1] == "+"
- diff = sbs? type, 'add'
- @before = 'add'
- diff.line_left = escapeHTML line[1..-1]
- diff.nb_line_left = @line_num_l
- diff.type_diff_left = 'diff_in'
- @line_num_l += 1
- true
- elsif line[0, 1] == "-"
- diff = sbs? type, 'remove'
- @before = 'remove'
- diff.line_right = escapeHTML line[1..-1]
- diff.nb_line_right = @line_num_r
- diff.type_diff_right = 'diff_out'
- @line_num_r += 1
- true
- elsif line[0, 1] =~ /\s/
- @before = 'same'
- @start = false
- diff = Diff.new
- diff.line_right = escapeHTML line[1..-1]
- diff.nb_line_right = @line_num_r
- diff.line_left = escapeHTML line[1..-1]
- diff.nb_line_left = @line_num_l
- self[@nb_line] = diff
- @line_num_l += 1
- @line_num_r += 1
- true
- else
- false
- end
- end
- end
-end \ No newline at end of file
diff --git a/app/views/projects/_form.rhtml b/app/views/projects/_form.rhtml
index 5f253d401..9eb933035 100644
--- a/app/views/projects/_form.rhtml
+++ b/app/views/projects/_form.rhtml
@@ -27,17 +27,17 @@
<!--[eoform:project]-->
</div>
-<div class="box"><h3><%= check_box_tag "repository_enabled", 1, !@project.repository.nil?, :onclick => "Element.toggle('repository');" %> <%= l(:label_repository) %></h3>
-<%= hidden_field_tag "repository_enabled", 0 %>
-<div id="repository">
-<% fields_for :repository, @project.repository, { :builder => TabularFormBuilder, :lang => current_language} do |repository| %>
-<p><%= repository.text_field :url, :size => 60, :required => true, :disabled => (@project.repository && !@project.repository.root_url.blank?) %><br />(http://, https://, svn://, file:///)</p>
-<p><%= repository.text_field :login, :size => 30 %></p>
-<p><%= repository.password_field :password, :size => 30 %></p>
-<% end %>
+<div class="box">
+ <h3><%= check_box_tag "repository_enabled", 1, !@project.repository.nil?, :onclick => "Element.toggle('repository');" %> <%= l(:label_repository) %></h3>
+ <%= hidden_field_tag "repository_enabled", 0 %>
+ <div id="repository">
+ <p class="tabular"><label>SCM</label><%= scm_select_tag %></p>
+ <div id="repository_fields">
+ <%= render :partial => 'projects/repository', :locals => {:repository => @project.repository} if @project.repository %>
+ </div>
+ </div>
</div>
<%= javascript_tag "Element.hide('repository');" if @project.repository.nil? %>
-</div>
<div class="box">
<h3><%= check_box_tag "wiki_enabled", 1, !@project.wiki.nil?, :onclick => "Element.toggle('wiki');" %> <%= l(:label_wiki) %></h3>
@@ -58,4 +58,4 @@
<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %>
<%= javascript_include_tag 'calendar/calendar-setup' %>
<%= stylesheet_link_tag 'calendar' %>
-<% end %> \ No newline at end of file
+<% end %>
diff --git a/app/views/projects/_repository.rhtml b/app/views/projects/_repository.rhtml
new file mode 100644
index 000000000..6f79c615f
--- /dev/null
+++ b/app/views/projects/_repository.rhtml
@@ -0,0 +1,3 @@
+<% fields_for :repository, repository, { :builder => TabularFormBuilder, :lang => current_language} do |f| %>
+<%= repository_field_tags(f, repository) %>
+<% end %>
diff --git a/app/views/repositories/_dir_list.rhtml b/app/views/repositories/_dir_list.rhtml
index 0e5b712bf..5555ee87e 100644
--- a/app/views/repositories/_dir_list.rhtml
+++ b/app/views/repositories/_dir_list.rhtml
@@ -11,15 +11,15 @@
<% total_size = 0
@entries.each do |entry| %>
<tr class="<%= cycle 'odd', 'even' %>">
-<td><%= link_to h(entry.name), { :action => (entry.is_dir? ? 'browse' : 'revisions'), :id => @project, :path => entry.path, :rev => @rev }, :class => ("icon " + (entry.is_dir? ? 'icon-folder' : 'icon-file')) %></td>
-<td align="right"><%= number_to_human_size(entry.size) unless entry.is_dir? %></td>
-<td align="right"><%= link_to entry.lastrev.identifier, :action => 'revision', :id => @project, :rev => entry.lastrev.identifier %></td>
-<td align="center"><%= format_time(entry.lastrev.time) %></td>
-<td align="center"><em><%=h entry.lastrev.author %></em></td>
-<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) %>
+<td><%= link_to h(entry.name), { :action => (entry.is_dir? ? 'browse' : 'changes'), :id => @project, :path => entry.path, :rev => @rev }, :class => ("icon " + (entry.is_dir? ? 'icon-folder' : 'icon-file')) %></td>
+<td align="right"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
+<td align="right"><%= link_to(entry.lastrev.name, :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %></td>
+<td align="center"><%= format_time(entry.lastrev.time) if entry.lastrev %></td>
+<td align="center"><em><%=h(entry.lastrev.author) if entry.lastrev %></em></td>
+<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev %>
<td><%=h truncate(changeset.comments, 100) unless changeset.nil? %></td>
</tr>
-<% total_size += entry.size
+<% total_size += entry.size if entry.size
end %>
</tbody>
</table>
diff --git a/app/views/repositories/_navigation.rhtml b/app/views/repositories/_navigation.rhtml
index efc0b9ff9..823b4f44f 100644
--- a/app/views/repositories/_navigation.rhtml
+++ b/app/views/repositories/_navigation.rhtml
@@ -5,7 +5,8 @@ if 'file' == kind
filename = dirs.pop
end
link_path = ''
-dirs.each do |dir|
+dirs.each do |dir|
+ next if dir.blank?
link_path << '/' unless link_path.empty?
link_path << "#{dir}"
%>
@@ -15,4 +16,4 @@ dirs.each do |dir|
/ <%= link_to h(filename), :action => 'revisions', :id => @project, :path => "#{link_path}/#{filename}", :rev => @rev %>
<% end %>
-<%= "@ #{revision}" if revision %> \ No newline at end of file
+<%= "@ #{revision}" if revision %>
diff --git a/app/views/repositories/_revisions.rhtml b/app/views/repositories/_revisions.rhtml
index faec16662..b2bdb6c7f 100644
--- a/app/views/repositories/_revisions.rhtml
+++ b/app/views/repositories/_revisions.rhtml
@@ -9,12 +9,13 @@
<th><%= l(:field_comments) %></th>
</tr></thead>
<tbody>
-<% show_diff = entry && entry.is_file? && changesets.size > 1 %>
+<% show_diff = entry && entry.is_file? && revisions.size > 1 %>
<% line_num = 1 %>
-<% changesets.each do |changeset| %>
+<% revisions.each do |revision| %>
+<% changeset = revision.is_a?(Change) ? revision.changeset : revision %>
<tr class="<%= cycle 'odd', 'even' %>">
-<th align="center" style="width:3em;"><%= link_to changeset.revision, :action => 'revision', :id => project, :rev => changeset.revision %></th>
-<td align="center" style="width:1em;"><%= radio_button_tag('rev', changeset.revision, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < changesets.size) %></td>
+<th align="center" style="width:3em;"><%= link_to (revision.revision || changeset.revision), :action => 'revision', :id => project, :rev => changeset.revision %></th>
+<td align="center" style="width:1em;"><%= radio_button_tag('rev', changeset.revision, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td>
<td align="center" style="width:1em;"><%= radio_button_tag('rev_to', changeset.revision, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td>
<td align="center" style="width:15%"><%= format_time(changeset.committed_on) %></td>
<td align="center" style="width:15%"><em><%=h changeset.committer %></em></td>
diff --git a/app/views/repositories/changes.rhtml b/app/views/repositories/changes.rhtml
new file mode 100644
index 000000000..35ce939fc
--- /dev/null
+++ b/app/views/repositories/changes.rhtml
@@ -0,0 +1,13 @@
+<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
+
+<h3><%=h @entry.name %></h3>
+
+<p>
+<% if @entry.is_text? %>
+<%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> |
+<% end %>
+<%= link_to l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' } %>
+<%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
+</p>
+
+<%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changes, :entry => @entry }%>
diff --git a/app/views/repositories/revision.rhtml b/app/views/repositories/revision.rhtml
index 5cf5c2e41..b484becce 100644
--- a/app/views/repositories/revision.rhtml
+++ b/app/views/repositories/revision.rhtml
@@ -7,7 +7,9 @@
<h2><%= l(:label_revision) %> <%= @changeset.revision %></h2>
-<p><em><%= @changeset.committer %>, <%= format_time(@changeset.committed_on) %></em></p>
+<p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
+<em><%= @changeset.committer %>, <%= format_time(@changeset.committed_on) %></em></p>
+
<%= textilizable @changeset.comments %>
<% if @changeset.issues.any? %>
@@ -30,7 +32,7 @@
<tbody>
<% @changes.each do |change| %>
<tr class="<%= cycle 'odd', 'even' %>">
-<td><div class="square action_<%= change.action %>"></div> <%= change.path %></td>
+<td><div class="square action_<%= change.action %>"></div> <%= change.path %> <%= "(#{change.revision})" unless change.revision.blank? %></td>
<td align="right">
<% if change.action == "M" %>
<%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => change.path, :rev => @changeset.revision %>
diff --git a/app/views/repositories/revisions.rhtml b/app/views/repositories/revisions.rhtml
index 4a5b3766e..0c2655d5f 100644
--- a/app/views/repositories/revisions.rhtml
+++ b/app/views/repositories/revisions.rhtml
@@ -5,25 +5,13 @@
<% end %>
</div>
-<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
+<h2><%= l(:label_revision_plural) %></h2>
-<% if @entry && @entry.is_file? %>
-<h3><%=h @entry.name %></h3>
-<p>
-<% if @entry.is_text? %>
-<%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> |
-<% end %>
-<%= link_to l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' } %>
-(<%= number_to_human_size @entry.size %>)</p>
-<% end %>
-
-<h3><%= l(:label_revision_plural) %></h3>
-
-<%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :changesets => @changesets, :entry => @entry }%>
+<%= render :partial => 'revisions', :locals => {:project => @project, :path => '', :revisions => @changesets, :entry => nil }%>
<p><%= pagination_links_full @changeset_pages %>
[ <%= @changeset_pages.current.first_item %> - <%= @changeset_pages.current.last_item %> / <%= @changeset_count %> ]</p>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "scm" %>
-<% end %> \ No newline at end of file
+<% end %>
diff --git a/app/views/repositories/show.rhtml b/app/views/repositories/show.rhtml
index 04a58b4c9..fcf954473 100644
--- a/app/views/repositories/show.rhtml
+++ b/app/views/repositories/show.rhtml
@@ -2,17 +2,19 @@
<%= link_to l(:label_statistics), {:action => 'stats', :id => @project}, :class => 'icon icon-stats' %>
</div>
-<h2><%= l(:label_repository) %></h2>
+<h2><%= l(:label_repository) %> (<%= @repository.scm_name %>)</h2>
+<% unless @entries.nil? %>
<h3><%= l(:label_browse) %></h3>
<%= render :partial => 'dir_list' %>
+<% end %>
<% unless @changesets.empty? %>
<h3><%= l(:label_latest_revision_plural) %></h3>
-<%= render :partial => 'revisions', :locals => {:project => @project, :path => '', :changesets => @changesets, :entry => nil }%>
+<%= render :partial => 'revisions', :locals => {:project => @project, :path => '', :revisions => @changesets, :entry => nil }%>
<p><%= link_to l(:label_view_revisions), :action => 'revisions', :id => @project %></p>
<% end %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "scm" %>
-<% end %> \ No newline at end of file
+<% end %>