Commit 438161ad authored by Jean-Philippe Lang's avatar Jean-Philippe Lang

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
parent 4dddb606
......@@ -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]
......
......@@ -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
......@@ -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))
......
......@@ -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
......@@ -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
# 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
# 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
# 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
This diff is collapsed.
......@@ -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 %>
<% fields_for :repository, repository, { :builder => TabularFormBuilder, :lang => current_language} do |f| %>
<%= repository_field_tags(f, repository) %>
<% end %>
......@@ -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>
......
......@@ -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 %>
......@@ -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>
......
<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 }%>
......@@ -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 %>
......
......@@ -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 %>
......@@ -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 %>
class AddChangesRevision < ActiveRecord::Migration
def self.up
add_column :changes, :revision, :string
end
def self.down
remove_column :changes, :revision
end
end
class AddChangesBranch < ActiveRecord::Migration
def self.up
add_column :changes, :branch, :string
end
def self.down
remove_column :changes, :branch
end
end
class AddChangesetsScmid < ActiveRecord::Migration
def self.up
add_column :changesets, :scmid, :string
end
def self.down
remove_column :changesets, :scmid
end
end
class AddRepositoriesType < ActiveRecord::Migration
def self.up
add_column :repositories, :type, :string
# Set class name for existing SVN repositories
Repository.update_all "type = 'Subversion'"
end
def self.down
remove_column :repositories, :type
end
end
class AddRepositoriesChangesPermission < ActiveRecord::Migration
def self.up
Permission.create :controller => 'repositories', :action => 'changes', :description => 'label_change_plural', :sort => 1475, :is_public => true, :mail_option => 0, :mail_enabled => 0
end
def self.down
Permission.find_by_controller_and_action('repositories', 'changes').destroy
end
end
......@@ -167,8 +167,8 @@ setting_host_name: Хост
setting_text_formatting: Форматиране на текста
setting_wiki_compression: Wiki компресиране на историята
setting_feeds_limit: Лимит на Feeds
setting_autofetch_changesets: Автоматично обработване на commits в SVN склада
setting_sys_api_enabled: Разрешаване на WS за управление на SVN склада
setting_autofetch_changesets: Автоматично обработване на commits в склада
setting_sys_api_enabled: Разрешаване на WS за управление на склада
setting_commit_ref_keywords: Отбелязващи ключови думи
setting_commit_fix_keywords: Приключващи ключови думи
setting_autologin: Autologin
......@@ -318,7 +318,7 @@ label_ago: преди дни
label_contains: съдържа
label_not_contains: не съдържа
label_day_plural: дни
label_repository: SVN Склад
label_repository: Склад
label_browse: Разглеждане
label_modification: %d промяна
label_modification_plural: %d промени
......
......@@ -67,7 +67,7 @@ notice_successful_delete: Erfolgreiche Löschung.
notice_successful_connection: Verbindung erfolgreich.
notice_file_not_found: Anhang besteht nicht oder ist gelöscht worden.
notice_locking_conflict: Datum wurde von einem anderen Benutzer geändert.
notice_scm_error: Eintrag und/oder Revision besteht nicht im SVN.
notice_scm_error: Eintrag und/oder Revision besteht nicht im Projektarchiv.
notice_not_authorized: You are not authorized to access this page.
mail_subject_lost_password: Ihr redMine Kennwort
......@@ -167,7 +167,7 @@ setting_host_name: Host Name
setting_text_formatting: Textformatierung
setting_wiki_compression: Wiki-Historie komprimieren
setting_feeds_limit: Limit Feed Inhalt
setting_autofetch_changesets: Autofetch SVN commits
setting_autofetch_changesets: Autofetch commits
setting_sys_api_enabled: Enable WS for repository management
setting_commit_ref_keywords: Referencing keywords
setting_commit_fix_keywords: Fixing keywords
......@@ -318,7 +318,7 @@ label_ago: vor
label_contains: enthält
label_not_contains: enthält nicht
label_day_plural: Tage
label_repository: SVN Projektarchiv
label_repository: Projektarchiv
label_browse: Codebrowser
label_modification: %d Änderung
label_modification_plural: %d Änderungen
......
......@@ -167,7 +167,7 @@ setting_host_name: Host name
setting_text_formatting: Text formatting
setting_wiki_compression: Wiki history compression
setting_feeds_limit: Feed content limit
setting_autofetch_changesets: Autofetch SVN commits
setting_autofetch_changesets: Autofetch commits
setting_sys_api_enabled: Enable WS for repository management
setting_commit_ref_keywords: Referencing keywords
setting_commit_fix_keywords: Fixing keywords
......@@ -318,7 +318,7 @@ label_ago: days ago
label_contains: contains
label_not_contains: doesn't contain
label_day_plural: days
label_repository: SVN Repository
label_repository: Repository
label_browse: Browse
label_modification: %d change
label_modification_plural: %d changes
......
......@@ -167,7 +167,7 @@ setting_host_name: Nombre de anfitrión
setting_text_formatting: Formato de texto
setting_wiki_compression: Compresión de la historia de Wiki
setting_feeds_limit: Feed content limit
setting_autofetch_changesets: Autofetch SVN commits
setting_autofetch_changesets: Autofetch commits
setting_sys_api_enabled: Enable WS for repository management
setting_commit_ref_keywords: Referencing keywords
setting_commit_fix_keywords: Fixing keywords
......@@ -318,7 +318,7 @@ label_ago: hace
label_contains: contiene
label_not_contains: no contiene
label_day_plural: días
label_repository: Depósito SVN
label_repository: Depósito
label_browse: Hojear
label_modification: %d modificación
label_modification_plural: %d modificaciones
......
......@@ -167,7 +167,7 @@ setting_host_name: Nom d'hôte
setting_text_formatting: Formatage du texte
setting_wiki_compression: Compression historique wiki
setting_feeds_limit: Limite du contenu des flux RSS
setting_autofetch_changesets: Récupération auto. des commits SVN
setting_autofetch_changesets: Récupération auto. des commits
setting_sys_api_enabled: Activer les WS pour la gestion des dépôts
setting_commit_ref_keywords: Mot-clés de référencement
setting_commit_fix_keywords: Mot-clés de résolution
......@@ -318,7 +318,7 @@ 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_repository: Dépôt
label_browse: Parcourir
label_modification: %d modification
label_modification_plural: %d modifications
......@@ -450,7 +450,7 @@ text_length_between: Longueur comprise entre %d et %d caractères.
text_tracker_no_workflow: Aucun worflow n'est défini pour ce tracker
text_unallowed_characters: Caractères non autorisés
text_comma_separated: Plusieurs valeurs possibles (séparées par des virgules).
text_issues_ref_in_commit_messages: Référencement et résolution des demandes dans les commentaires SVN
text_issues_ref_in_commit_messages: Référencement et résolution des demandes dans les commentaires de commits
default_role_manager: Manager
default_role_developper: Développeur
......
......@@ -167,7 +167,7 @@ setting_host_name: Nome host
setting_text_formatting: Formattazione testo
setting_wiki_compression: Compressione di storia di Wiki
setting_feeds_limit: Limite contenuti del feed
setting_autofetch_changesets: Acquisisci automaticamente le commit SVN
setting_autofetch_changesets: Acquisisci automaticamente le commit
setting_sys_api_enabled: Abilita WS per la gestione del repository
setting_commit_ref_keywords: Referencing keywords
setting_commit_fix_keywords: Fixing keywords
......@@ -318,7 +318,7 @@ label_ago: giorni fa
label_contains: contiene
label_not_contains: non contiene
label_day_plural: giorni
label_repository: SVN Repository
label_repository: Repository
label_browse: Browse
label_modification: %d modifica
label_modification_plural: %d modifiche
......
......@@ -168,7 +168,7 @@ setting_host_name: ホスト名
setting_text_formatting: テキストの書式
setting_wiki_compression: Wiki履歴を圧縮する
setting_feeds_limit: フィード内容の上限
setting_autofetch_changesets: SVNコミットを自動取得する
setting_autofetch_changesets: コミットを自動取得する
setting_sys_api_enabled: リポジトリ管理用のWeb Serviceを有効化する
setting_commit_ref_keywords: 参照用キーワード
setting_commit_fix_keywords: 修正用キーワード
......@@ -319,7 +319,7 @@ label_ago: 日前
label_contains: 含む
label_not_contains: 含まない
label_day_plural:
label_repository: SVNリポジトリ
label_repository: リポジトリ
label_browse: ブラウズ
label_modification: %d点の変更
label_modification_plural: %d点の変更
......
......@@ -167,7 +167,7 @@ setting_host_name: Host naam
setting_text_formatting: Tekst formaat
setting_wiki_compression: Wiki geschiedenis comprimeren
setting_feeds_limit: Feed inhoud limiet
setting_autofetch_changesets: Haal SVN commits automatisch op
setting_autofetch_changesets: Haal commits automatisch op
setting_sys_api_enabled: Gebruik WS voor repository beheer
setting_commit_ref_keywords: Referencing keywords
setting_commit_fix_keywords: Fixing keywords
......@@ -318,7 +318,7 @@ label_ago: dagen geleden
label_contains: bevat
label_not_contains: bevat niet
label_day_plural: dagen
label_repository: SVN Repository
label_repository: Repository
label_browse: Blader
label_modification: %d wijziging
label_modification_plural: %d wijzigingen
......
......@@ -167,7 +167,7 @@ setting_host_name: Servidor
setting_text_formatting: Formato do texto
setting_wiki_compression: Compactacao do historio do Wiki
setting_feeds_limit: Limite do Feed
setting_autofetch_changesets: Autofetch SVN commits
setting_autofetch_changesets: Autofetch commits
setting_sys_api_enabled: Ativa WS para gerenciamento do repositorio
setting_commit_ref_keywords: Referencing keywords
setting_commit_fix_keywords: Fixing keywords
......@@ -318,7 +318,7 @@ label_ago: dias atras
label_contains: contem
label_not_contains: nao contem
label_day_plural: dias
label_repository: SVN Repository
label_repository: Repository
label_browse: Browse
label_modification: %d change
label_modification_plural: %d changes
......
......@@ -167,7 +167,7 @@ setting_host_name: Servidor
setting_text_formatting: Formato do texto
setting_wiki_compression: Compactação do histórico do Wiki
setting_feeds_limit: Limite do Feed
setting_autofetch_changesets: Buscar automaticamente commits do SVN
setting_autofetch_changesets: Buscar automaticamente commits
setting_sys_api_enabled: Ativa WS para gerenciamento do repositório
setting_commit_ref_keywords: Palavras-chave de referôncia
setting_commit_fix_keywords: Palavras-chave fixas
......@@ -318,7 +318,7 @@ label_ago: dias atrás
label_contains: contém
label_not_contains: não contém
label_day_plural: dias
label_repository: Repositório SVN
label_repository: Repositório
label_browse: Procurar
label_modification: %d mudança
label_modification_plural: %d mudanças
......
......@@ -167,7 +167,7 @@ setting_host_name: Värddatornamn
setting_text_formatting: Textformattering
setting_wiki_compression: Wiki historiekomprimering
setting_feeds_limit: Feed innehållsgräns
setting_autofetch_changesets: Automatisk hämtning av SVN commits
setting_autofetch_changesets: Automatisk hämtning av commits
setting_sys_api_enabled: Aktivera WS för repository management
setting_commit_ref_keywords: Referencing keywords
setting_commit_fix_keywords: Fixing keywords
......@@ -318,7 +318,7 @@ label_ago: dagar sedan
label_contains: innehåller
label_not_contains: innehåller inte
label_day_plural: dagar
label_repository: SVN Repositorie
label_repository: Repositorie
label_browse: Bläddra
label_modification: %d ändring
label_modification_plural: %d ändringar
......
......@@ -170,7 +170,7 @@ setting_host_name: 主机名称
setting_text_formatting: 文本格式
setting_wiki_compression: Wiki history compression
setting_feeds_limit: Feed content limit
setting_autofetch_changesets: Autofetch SVN commits
setting_autofetch_changesets: Autofetch commits
setting_sys_api_enabled: Enable WS for repository management
setting_commit_ref_keywords: Referencing keywords
setting_commit_fix_keywords: Fixing keywords
......@@ -321,7 +321,7 @@ label_ago: 之前天数
label_contains: 包含
label_not_contains: 不包含
label_day_plural: 天数
label_repository: SVN 版本库
label_repository: 版本库
label_browse: 浏览
label_modification: %d 个更新
label_modification_plural: %d 个更新
......
require 'redmine/version'
require 'redmine/mime_type'
require 'redmine/acts_as_watchable/init'
REDMINE_SUPPORTED_SCM = %w( Subversion Mercurial Cvs )
This diff is collapsed.
This diff is collapsed.
# 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/abstract_adapter'
module Redmine
module Scm
module Adapters
class MercurialAdapter < AbstractAdapter
# Mercurial executable name
HG_BIN = "hg"
def info
cmd = "#{HG_BIN} -R #{target('')} root"
root_url = nil
shellout(cmd) do |io|
root_url = io.gets
end
return nil if $? && $?.exitstatus != 0
info = Info.new({:root_url => root_url.chomp,
:lastrev => revisions(nil,nil,nil,{:limit => 1}).last
})
info
rescue Errno::ENOENT => e
return nil
end
def entries(path=nil, identifier=nil)
path ||= ''
entries = Entries.new
cmd = "#{HG_BIN} -R #{target('')} --cwd #{target(path)} locate -X */*/*"
cmd << " -r #{identifier.to_i}" if identifier
cmd << " * */*"
shellout(cmd) do |io|
io.each_line do |line|
e = line.chomp.split('\\')
entries << Entry.new({:name => e.first,
:path => (path.empty? ? e.first : "#{path}/#{e.first}"),
:kind => (e.size > 1 ? 'dir' : 'file'),
:lastrev => Revision.new
}) unless entries.detect{|entry| entry.name == e.first}
end
end
return nil if $? && $?.exitstatus != 0
entries.sort_by_name
rescue Errno::ENOENT => e
raise CommandFailed
end
def entry(path=nil, identifier=nil)
path ||= ''
search_path = path.split('/')[0..-2].join('/')
entry_name = path.split('/').last
e = entries(search_path, identifier)
e ? e.detect{|entry| entry.name == entry_name} : nil
end
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
revisions = Revisions.new
cmd = "#{HG_BIN} -v -R #{target('')} log"
cmd << " -r #{identifier_from.to_i}:" if identifier_from
cmd << " --limit #{options[:limit].to_i}" if options[:limit]
shellout(cmd) do |io|
changeset = {}
parsing_descr = false
line_feeds = 0
io.each_line do |line|
if line =~ /^(\w+):\s*(.*)$/
key = $1
value = $2
if parsing_descr && line_feeds > 1
parsing_descr = false
revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i,
:scmid => changeset[:changeset].split(':').last,
:author => changeset[:user],
:time => Time.parse(changeset[:date]),
:message => changeset[:description],
:paths => changeset[:files].split.collect{|path| {:action => 'X', :path => "/#{path}"}}
})
changeset = {}
end
if !parsing_descr
changeset.store key.to_sym, value
if $1 == "description"
parsing_descr = true
line_feeds = 0
next
end
end
end
if parsing_descr
changeset[:description] << line
line_feeds += 1 if line.chomp.empty?
end
end
revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i,
:scmid => changeset[:changeset].split(':').last,
:author => changeset[:user],
:time => Time.parse(changeset[:date]),
:message => changeset[:description],
:paths => changeset[:files].split.collect{|path| {:action => 'X', :path => "/#{path}"}}
})
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
identifier_to = identifier_to.to_i
else
identifier_to = identifier_from.to_i - 1
end
cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
cmd << " -I #{target(path)}" unless path.empty?
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)
cmd = "#{HG_BIN} -R #{target('')} cat #{target(path)}"
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
end
end
end
end
# 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/abstract_adapter'
require 'rexml/document'
module Redmine
module Scm
module Adapters
class SubversionAdapter < AbstractAdapter
# SVN executable name
SVN_BIN = "svn"
# Get info about the svn repository
def info
cmd = "#{SVN_BIN} 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_BIN} 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_BIN} 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_BIN} 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_BIN} 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
end
end
end
end
......@@ -25,7 +25,7 @@ class RepositoryTest < Test::Unit::TestCase
end
def test_create
repository = Repository.new(:project => Project.find(2))
repository = Repository::Subversion.new(:project => Project.find(2))
assert !repository.save
repository.url = "svn://localhost"
......@@ -34,12 +34,6 @@ class RepositoryTest < Test::Unit::TestCase
project = Project.find(2)
assert_equal repository, project.repository
end
def test_cant_change_url
url = @repository.url
@repository.url = "svn://anotherhost"
assert_equal url, @repository.url
end
def test_scan_changesets_for_issue_ids
......@@ -59,12 +53,4 @@ class RepositoryTest < Test::Unit::TestCase
# ignoring commits referencing an issue of another project
assert_equal [], Issue.find(4).changesets
end
def test_changesets_with_path
@repository.changesets_with_path '/some/path' do
assert_equal 1, @repository.changesets.count(:select => "DISTINCT #{Changeset.table_name}.id")
changesets = @repository.changesets.find(:all)
assert_equal 1, changesets.size
end
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment