##// END OF EJS Templates
Added Darcs basic support....
Jean-Philippe Lang -
r570:ba1b857197ab
parent child
Show More
@@ -0,0 +1,90
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require 'redmine/scm/adapters/darcs_adapter'
19
20 class Repository::Darcs < Repository
21 validates_presence_of :url
22
23 def scm_adapter
24 Redmine::Scm::Adapters::DarcsAdapter
25 end
26
27 def self.scm_name
28 'Darcs'
29 end
30
31 def entries(path=nil, identifier=nil)
32 entries=scm.entries(path, identifier)
33 if entries
34 entries.each do |entry|
35 # Search the DB for the entry's last change
36 changeset = changesets.find_by_scmid(entry.lastrev.scmid) if entry.lastrev && !entry.lastrev.scmid.blank?
37 if changeset
38 entry.lastrev.identifier = changeset.revision
39 entry.lastrev.name = changeset.revision
40 entry.lastrev.time = changeset.committed_on
41 entry.lastrev.author = changeset.committer
42 end
43 end
44 end
45 entries
46 end
47
48 def diff(path, rev, rev_to, type)
49 patch_from = changesets.find_by_revision(rev)
50 patch_to = changesets.find_by_revision(rev_to) if rev_to
51 if path.blank?
52 path = patch_from.changes.collect{|change| change.path}.join(' ')
53 end
54 scm.diff(path, patch_from.scmid, patch_to.scmid, type)
55 end
56
57 def fetch_changesets
58 scm_info = scm.info
59 if scm_info
60 db_last_id = latest_changeset ? latest_changeset.scmid : nil
61 next_rev = latest_changeset ? latest_changeset.revision + 1 : 1
62 # latest revision in the repository
63 scm_revision = scm_info.lastrev.scmid
64 unless changesets.find_by_scmid(scm_revision)
65 revisions = scm.revisions('', db_last_id, nil, :with_path => true)
66 transaction do
67 revisions.reverse_each do |revision|
68 changeset = Changeset.create(:repository => self,
69 :revision => next_rev,
70 :scmid => revision.scmid,
71 :committer => revision.author,
72 :committed_on => revision.time,
73 :comments => revision.message)
74
75 next if changeset.new_record?
76
77 revision.paths.each do |change|
78 Change.create(:changeset => changeset,
79 :action => change[:action],
80 :path => change[:path],
81 :from_path => change[:from_path],
82 :from_revision => change[:from_revision])
83 end
84 next_rev += 1
85 end if revisions
86 end
87 end
88 end
89 end
90 end
@@ -0,0 +1,163
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require 'redmine/scm/adapters/abstract_adapter'
19 require 'rexml/document'
20
21 module Redmine
22 module Scm
23 module Adapters
24 class DarcsAdapter < AbstractAdapter
25 # Darcs executable name
26 DARCS_BIN = "darcs"
27
28 def initialize(url, root_url=nil, login=nil, password=nil)
29 @url = url
30 @root_url = url
31 end
32
33 def supports_cat?
34 false
35 end
36
37 # Get info about the svn repository
38 def info
39 rev = revisions(nil,nil,nil,{:limit => 1})
40 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
41 end
42
43 # Returns the entry identified by path and revision identifier
44 # or nil if entry doesn't exist in the repository
45 def entry(path=nil, identifier=nil)
46 e = entries(path, identifier)
47 e ? e.first : nil
48 end
49
50 # Returns an Entries collection
51 # or nil if the given path doesn't exist in the repository
52 def entries(path=nil, identifier=nil)
53 path_prefix = (path.blank? ? '' : "#{path}/")
54 path = '.' if path.blank?
55 entries = Entries.new
56 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output #{path}"
57 shellout(cmd) do |io|
58 begin
59 doc = REXML::Document.new(io)
60 if doc.root.name == 'directory'
61 doc.elements.each('directory/*') do |element|
62 next unless ['file', 'directory'].include? element.name
63 entries << entry_from_xml(element, path_prefix)
64 end
65 elsif doc.root.name == 'file'
66 entries << entry_from_xml(doc.root, path_prefix)
67 end
68 rescue
69 end
70 end
71 return nil if $? && $?.exitstatus != 0
72 entries.sort_by_name
73 rescue Errno::ENOENT => e
74 raise CommandFailed
75 end
76
77 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
78 path = '.' if path.blank?
79 revisions = Revisions.new
80 cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
81 cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from
82 cmd << " --last #{options[:limit].to_i}" if options[:limit]
83 shellout(cmd) do |io|
84 begin
85 doc = REXML::Document.new(io)
86 doc.elements.each("changelog/patch") do |patch|
87 message = patch.elements['name'].text
88 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
89 revisions << Revision.new({:identifier => nil,
90 :author => patch.attributes['author'],
91 :scmid => patch.attributes['hash'],
92 :time => Time.parse(patch.attributes['local_date']),
93 :message => message,
94 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
95 })
96 end
97 rescue
98 end
99 end
100 return nil if $? && $?.exitstatus != 0
101 revisions
102 rescue Errno::ENOENT => e
103 raise CommandFailed
104 end
105
106 def diff(path, identifier_from, identifier_to=nil, type="inline")
107 path = '*' if path.blank?
108 cmd = "#{DARCS_BIN} diff --repodir #{@url}"
109 cmd << " --to-match \"hash #{identifier_from}\""
110 cmd << " --from-match \"hash #{identifier_to}\"" if identifier_to
111 cmd << " -u #{path}"
112 diff = []
113 shellout(cmd) do |io|
114 io.each_line do |line|
115 diff << line
116 end
117 end
118 return nil if $? && $?.exitstatus != 0
119 DiffTableList.new diff, type
120 rescue Errno::ENOENT => e
121 raise CommandFailed
122 end
123
124 private
125
126 def entry_from_xml(element, path_prefix)
127 Entry.new({:name => element.attributes['name'],
128 :path => path_prefix + element.attributes['name'],
129 :kind => element.name == 'file' ? 'file' : 'dir',
130 :size => nil,
131 :lastrev => Revision.new({
132 :identifier => nil,
133 :scmid => element.elements['modified'].elements['patch'].attributes['hash']
134 })
135 })
136 end
137
138 # Retrieve changed paths for a single patch
139 def get_paths_for_patch(hash)
140 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
141 cmd << " --match \"hash #{hash}\" "
142 paths = []
143 shellout(cmd) do |io|
144 begin
145 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
146 # A root element is added so that REXML doesn't raise an error
147 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
148 doc.elements.each('fake_root/summary/*') do |modif|
149 paths << {:action => modif.name[0,1].upcase,
150 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
151 }
152 end
153 rescue
154 end
155 end
156 paths
157 rescue Errno::ENOENT => e
158 paths
159 end
160 end
161 end
162 end
163 end
@@ -1,54 +1,58
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module RepositoriesHelper
18 module RepositoriesHelper
19 def repository_field_tags(form, repository)
19 def repository_field_tags(form, repository)
20 method = repository.class.name.demodulize.underscore + "_field_tags"
20 method = repository.class.name.demodulize.underscore + "_field_tags"
21 send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method)
21 send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method)
22 end
22 end
23
23
24 def scm_select_tag
24 def scm_select_tag
25 container = [[]]
25 container = [[]]
26 REDMINE_SUPPORTED_SCM.each {|scm| container << ["Repository::#{scm}".constantize.scm_name, scm]}
26 REDMINE_SUPPORTED_SCM.each {|scm| container << ["Repository::#{scm}".constantize.scm_name, scm]}
27 select_tag('repository_scm',
27 select_tag('repository_scm',
28 options_for_select(container, @project.repository.class.name.demodulize),
28 options_for_select(container, @project.repository.class.name.demodulize),
29 :disabled => (@project.repository && !@project.repository.new_record?),
29 :disabled => (@project.repository && !@project.repository.new_record?),
30 :onchange => remote_function(:update => "repository_fields", :url => { :controller => 'repositories', :action => 'update_form', :id => @project }, :with => "Form.serialize(this.form)")
30 :onchange => remote_function(:update => "repository_fields", :url => { :controller => 'repositories', :action => 'update_form', :id => @project }, :with => "Form.serialize(this.form)")
31 )
31 )
32 end
32 end
33
33
34 def with_leading_slash(path)
34 def with_leading_slash(path)
35 path ||= ''
35 path ||= ''
36 path.starts_with?("/") ? "/#{path}" : path
36 path.starts_with?("/") ? "/#{path}" : path
37 end
37 end
38
38
39 def subversion_field_tags(form, repository)
39 def subversion_field_tags(form, repository)
40 content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
40 content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
41 '<br />(http://, https://, svn://, file:///)') +
41 '<br />(http://, https://, svn://, file:///)') +
42 content_tag('p', form.text_field(:login, :size => 30)) +
42 content_tag('p', form.text_field(:login, :size => 30)) +
43 content_tag('p', form.password_field(:password, :size => 30))
43 content_tag('p', form.password_field(:password, :size => 30))
44 end
44 end
45
45
46 def darcs_field_tags(form, repository)
47 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
48 end
49
46 def mercurial_field_tags(form, repository)
50 def mercurial_field_tags(form, repository)
47 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
51 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
48 end
52 end
49
53
50 def cvs_field_tags(form, repository)
54 def cvs_field_tags(form, repository)
51 content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
55 content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
52 content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
56 content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
53 end
57 end
54 end
58 end
@@ -1,68 +1,69
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Changeset < ActiveRecord::Base
18 class Changeset < ActiveRecord::Base
19 belongs_to :repository
19 belongs_to :repository
20 has_many :changes, :dependent => :delete_all
20 has_many :changes, :dependent => :delete_all
21 has_and_belongs_to_many :issues
21 has_and_belongs_to_many :issues
22
22
23 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
23 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
24 validates_numericality_of :revision, :only_integer => true
24 validates_numericality_of :revision, :only_integer => true
25 validates_uniqueness_of :revision, :scope => :repository_id
25 validates_uniqueness_of :revision, :scope => :repository_id
26 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
26
27
27 def committed_on=(date)
28 def committed_on=(date)
28 self.commit_date = date
29 self.commit_date = date
29 super
30 super
30 end
31 end
31
32
32 def after_create
33 def after_create
33 scan_comment_for_issue_ids
34 scan_comment_for_issue_ids
34 end
35 end
35
36
36 def scan_comment_for_issue_ids
37 def scan_comment_for_issue_ids
37 return if comments.blank?
38 return if comments.blank?
38 # keywords used to reference issues
39 # keywords used to reference issues
39 ref_keywords = Setting.commit_ref_keywords.downcase.split(",")
40 ref_keywords = Setting.commit_ref_keywords.downcase.split(",")
40 # keywords used to fix issues
41 # keywords used to fix issues
41 fix_keywords = Setting.commit_fix_keywords.downcase.split(",")
42 fix_keywords = Setting.commit_fix_keywords.downcase.split(",")
42 # status applied
43 # status applied
43 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
44 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
44
45
45 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw.strip)}.join("|")
46 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw.strip)}.join("|")
46 return if kw_regexp.blank?
47 return if kw_regexp.blank?
47
48
48 # remove any associated issues
49 # remove any associated issues
49 self.issues.clear
50 self.issues.clear
50
51
51 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
52 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
52 action = match[0]
53 action = match[0]
53 target_issue_ids = match[1].scan(/\d+/)
54 target_issue_ids = match[1].scan(/\d+/)
54 target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
55 target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
55 if fix_status && fix_keywords.include?(action.downcase)
56 if fix_status && fix_keywords.include?(action.downcase)
56 # update status of issues
57 # update status of issues
57 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
58 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
58 target_issues.each do |issue|
59 target_issues.each do |issue|
59 # don't change the status is the issue is already closed
60 # don't change the status is the issue is already closed
60 next if issue.status.is_closed?
61 next if issue.status.is_closed?
61 issue.status = fix_status
62 issue.status = fix_status
62 issue.save
63 issue.save
63 end
64 end
64 end
65 end
65 self.issues << target_issues
66 self.issues << target_issues
66 end
67 end
67 end
68 end
68 end
69 end
@@ -1,75 +1,79
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Repository < ActiveRecord::Base
18 class Repository < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.revision DESC"
20 has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.revision DESC"
21 has_many :changes, :through => :changesets
21 has_many :changes, :through => :changesets
22
22
23 def scm
23 def scm
24 @scm ||= self.scm_adapter.new url, root_url, login, password
24 @scm ||= self.scm_adapter.new url, root_url, login, password
25 update_attribute(:root_url, @scm.root_url) if root_url.blank?
25 update_attribute(:root_url, @scm.root_url) if root_url.blank?
26 @scm
26 @scm
27 end
27 end
28
28
29 def scm_name
29 def scm_name
30 self.class.scm_name
30 self.class.scm_name
31 end
31 end
32
32
33 def supports_cat?
34 scm.supports_cat?
35 end
36
33 def entries(path=nil, identifier=nil)
37 def entries(path=nil, identifier=nil)
34 scm.entries(path, identifier)
38 scm.entries(path, identifier)
35 end
39 end
36
40
37 def diff(path, rev, rev_to, type)
41 def diff(path, rev, rev_to, type)
38 scm.diff(path, rev, rev_to, type)
42 scm.diff(path, rev, rev_to, type)
39 end
43 end
40
44
41 def latest_changeset
45 def latest_changeset
42 @latest_changeset ||= changesets.find(:first)
46 @latest_changeset ||= changesets.find(:first)
43 end
47 end
44
48
45 def scan_changesets_for_issue_ids
49 def scan_changesets_for_issue_ids
46 self.changesets.each(&:scan_comment_for_issue_ids)
50 self.changesets.each(&:scan_comment_for_issue_ids)
47 end
51 end
48
52
49 # fetch new changesets for all repositories
53 # fetch new changesets for all repositories
50 # can be called periodically by an external script
54 # can be called periodically by an external script
51 # eg. ruby script/runner "Repository.fetch_changesets"
55 # eg. ruby script/runner "Repository.fetch_changesets"
52 def self.fetch_changesets
56 def self.fetch_changesets
53 find(:all).each(&:fetch_changesets)
57 find(:all).each(&:fetch_changesets)
54 end
58 end
55
59
56 # scan changeset comments to find related and fixed issues for all repositories
60 # scan changeset comments to find related and fixed issues for all repositories
57 def self.scan_changesets_for_issue_ids
61 def self.scan_changesets_for_issue_ids
58 find(:all).each(&:scan_changesets_for_issue_ids)
62 find(:all).each(&:scan_changesets_for_issue_ids)
59 end
63 end
60
64
61 def self.scm_name
65 def self.scm_name
62 'Abstract'
66 'Abstract'
63 end
67 end
64
68
65 def self.available_scm
69 def self.available_scm
66 subclasses.collect {|klass| [klass.scm_name, klass.name]}
70 subclasses.collect {|klass| [klass.scm_name, klass.name]}
67 end
71 end
68
72
69 def self.factory(klass_name, *args)
73 def self.factory(klass_name, *args)
70 klass = "Repository::#{klass_name}".constantize
74 klass = "Repository::#{klass_name}".constantize
71 klass.new(*args)
75 klass.new(*args)
72 rescue
76 rescue
73 nil
77 nil
74 end
78 end
75 end
79 end
@@ -1,13 +1,15
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
2
2
3 <h3><%=h @entry.name %></h3>
3 <h3><%=h @entry.name %></h3>
4
4
5 <% if @repository.supports_cat? %>
5 <p>
6 <p>
6 <% if @entry.is_text? %>
7 <% if @entry.is_text? %>
7 <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> |
8 <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> |
8 <% end %>
9 <% end %>
9 <%= link_to l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' } %>
10 <%= link_to l(:button_download), {:action => 'entry', :id => @project, :path => @path, :rev => @rev, :format => 'raw' } %>
10 <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
11 <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
11 </p>
12 </p>
13 <% end %>
12
14
13 <%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changes, :entry => @entry }%>
15 <%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changes, :entry => @entry }%>
@@ -1,5 +1,5
1 require 'redmine/version'
1 require 'redmine/version'
2 require 'redmine/mime_type'
2 require 'redmine/mime_type'
3 require 'redmine/acts_as_watchable/init'
3 require 'redmine/acts_as_watchable/init'
4
4
5 REDMINE_SUPPORTED_SCM = %w( Subversion Mercurial Cvs )
5 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs )
@@ -1,341 +1,345
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'cgi'
18 require 'cgi'
19
19
20 module Redmine
20 module Redmine
21 module Scm
21 module Scm
22 module Adapters
22 module Adapters
23 class CommandFailed < StandardError #:nodoc:
23 class CommandFailed < StandardError #:nodoc:
24 end
24 end
25
25
26 class AbstractAdapter #:nodoc:
26 class AbstractAdapter #:nodoc:
27 def initialize(url, root_url=nil, login=nil, password=nil)
27 def initialize(url, root_url=nil, login=nil, password=nil)
28 @url = url
28 @url = url
29 @login = login if login && !login.empty?
29 @login = login if login && !login.empty?
30 @password = (password || "") if @login
30 @password = (password || "") if @login
31 @root_url = root_url.blank? ? retrieve_root_url : root_url
31 @root_url = root_url.blank? ? retrieve_root_url : root_url
32 end
32 end
33
33
34 def adapter_name
34 def adapter_name
35 'Abstract'
35 'Abstract'
36 end
36 end
37
37
38 def supports_cat?
39 true
40 end
41
38 def root_url
42 def root_url
39 @root_url
43 @root_url
40 end
44 end
41
45
42 def url
46 def url
43 @url
47 @url
44 end
48 end
45
49
46 # get info about the svn repository
50 # get info about the svn repository
47 def info
51 def info
48 return nil
52 return nil
49 end
53 end
50
54
51 # Returns the entry identified by path and revision identifier
55 # Returns the entry identified by path and revision identifier
52 # or nil if entry doesn't exist in the repository
56 # or nil if entry doesn't exist in the repository
53 def entry(path=nil, identifier=nil)
57 def entry(path=nil, identifier=nil)
54 e = entries(path, identifier)
58 e = entries(path, identifier)
55 e ? e.first : nil
59 e ? e.first : nil
56 end
60 end
57
61
58 # Returns an Entries collection
62 # Returns an Entries collection
59 # or nil if the given path doesn't exist in the repository
63 # or nil if the given path doesn't exist in the repository
60 def entries(path=nil, identifier=nil)
64 def entries(path=nil, identifier=nil)
61 return nil
65 return nil
62 end
66 end
63
67
64 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
68 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
65 return nil
69 return nil
66 end
70 end
67
71
68 def diff(path, identifier_from, identifier_to=nil, type="inline")
72 def diff(path, identifier_from, identifier_to=nil, type="inline")
69 return nil
73 return nil
70 end
74 end
71
75
72 def cat(path, identifier=nil)
76 def cat(path, identifier=nil)
73 return nil
77 return nil
74 end
78 end
75
79
76 def with_leading_slash(path)
80 def with_leading_slash(path)
77 path ||= ''
81 path ||= ''
78 (path[0,1]!="/") ? "/#{path}" : path
82 (path[0,1]!="/") ? "/#{path}" : path
79 end
83 end
80
84
81 private
85 private
82 def retrieve_root_url
86 def retrieve_root_url
83 info = self.info
87 info = self.info
84 info ? info.root_url : nil
88 info ? info.root_url : nil
85 end
89 end
86
90
87 def target(path)
91 def target(path)
88 path ||= ""
92 path ||= ""
89 base = path.match(/^\//) ? root_url : url
93 base = path.match(/^\//) ? root_url : url
90 " \"" << "#{base}/#{path}".gsub(/["?<>\*]/, '') << "\""
94 " \"" << "#{base}/#{path}".gsub(/["?<>\*]/, '') << "\""
91 end
95 end
92
96
93 def logger
97 def logger
94 RAILS_DEFAULT_LOGGER
98 RAILS_DEFAULT_LOGGER
95 end
99 end
96
100
97 def shellout(cmd, &block)
101 def shellout(cmd, &block)
98 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
102 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
99 IO.popen(cmd, "r+") do |io|
103 IO.popen(cmd, "r+") do |io|
100 io.close_write
104 io.close_write
101 block.call(io) if block_given?
105 block.call(io) if block_given?
102 end
106 end
103 end
107 end
104 end
108 end
105
109
106 class Entries < Array
110 class Entries < Array
107 def sort_by_name
111 def sort_by_name
108 sort {|x,y|
112 sort {|x,y|
109 if x.kind == y.kind
113 if x.kind == y.kind
110 x.name <=> y.name
114 x.name <=> y.name
111 else
115 else
112 x.kind <=> y.kind
116 x.kind <=> y.kind
113 end
117 end
114 }
118 }
115 end
119 end
116
120
117 def revisions
121 def revisions
118 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
122 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
119 end
123 end
120 end
124 end
121
125
122 class Info
126 class Info
123 attr_accessor :root_url, :lastrev
127 attr_accessor :root_url, :lastrev
124 def initialize(attributes={})
128 def initialize(attributes={})
125 self.root_url = attributes[:root_url] if attributes[:root_url]
129 self.root_url = attributes[:root_url] if attributes[:root_url]
126 self.lastrev = attributes[:lastrev]
130 self.lastrev = attributes[:lastrev]
127 end
131 end
128 end
132 end
129
133
130 class Entry
134 class Entry
131 attr_accessor :name, :path, :kind, :size, :lastrev
135 attr_accessor :name, :path, :kind, :size, :lastrev
132 def initialize(attributes={})
136 def initialize(attributes={})
133 self.name = attributes[:name] if attributes[:name]
137 self.name = attributes[:name] if attributes[:name]
134 self.path = attributes[:path] if attributes[:path]
138 self.path = attributes[:path] if attributes[:path]
135 self.kind = attributes[:kind] if attributes[:kind]
139 self.kind = attributes[:kind] if attributes[:kind]
136 self.size = attributes[:size].to_i if attributes[:size]
140 self.size = attributes[:size].to_i if attributes[:size]
137 self.lastrev = attributes[:lastrev]
141 self.lastrev = attributes[:lastrev]
138 end
142 end
139
143
140 def is_file?
144 def is_file?
141 'file' == self.kind
145 'file' == self.kind
142 end
146 end
143
147
144 def is_dir?
148 def is_dir?
145 'dir' == self.kind
149 'dir' == self.kind
146 end
150 end
147
151
148 def is_text?
152 def is_text?
149 Redmine::MimeType.is_type?('text', name)
153 Redmine::MimeType.is_type?('text', name)
150 end
154 end
151 end
155 end
152
156
153 class Revisions < Array
157 class Revisions < Array
154 def latest
158 def latest
155 sort {|x,y|
159 sort {|x,y|
156 unless x.time.nil? or y.time.nil?
160 unless x.time.nil? or y.time.nil?
157 x.time <=> y.time
161 x.time <=> y.time
158 else
162 else
159 0
163 0
160 end
164 end
161 }.last
165 }.last
162 end
166 end
163 end
167 end
164
168
165 class Revision
169 class Revision
166 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
170 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
167 def initialize(attributes={})
171 def initialize(attributes={})
168 self.identifier = attributes[:identifier]
172 self.identifier = attributes[:identifier]
169 self.scmid = attributes[:scmid]
173 self.scmid = attributes[:scmid]
170 self.name = attributes[:name] || self.identifier
174 self.name = attributes[:name] || self.identifier
171 self.author = attributes[:author]
175 self.author = attributes[:author]
172 self.time = attributes[:time]
176 self.time = attributes[:time]
173 self.message = attributes[:message] || ""
177 self.message = attributes[:message] || ""
174 self.paths = attributes[:paths]
178 self.paths = attributes[:paths]
175 self.revision = attributes[:revision]
179 self.revision = attributes[:revision]
176 self.branch = attributes[:branch]
180 self.branch = attributes[:branch]
177 end
181 end
178
182
179 end
183 end
180
184
181 # A line of Diff
185 # A line of Diff
182 class Diff
186 class Diff
183 attr_accessor :nb_line_left
187 attr_accessor :nb_line_left
184 attr_accessor :line_left
188 attr_accessor :line_left
185 attr_accessor :nb_line_right
189 attr_accessor :nb_line_right
186 attr_accessor :line_right
190 attr_accessor :line_right
187 attr_accessor :type_diff_right
191 attr_accessor :type_diff_right
188 attr_accessor :type_diff_left
192 attr_accessor :type_diff_left
189
193
190 def initialize ()
194 def initialize ()
191 self.nb_line_left = ''
195 self.nb_line_left = ''
192 self.nb_line_right = ''
196 self.nb_line_right = ''
193 self.line_left = ''
197 self.line_left = ''
194 self.line_right = ''
198 self.line_right = ''
195 self.type_diff_right = ''
199 self.type_diff_right = ''
196 self.type_diff_left = ''
200 self.type_diff_left = ''
197 end
201 end
198
202
199 def inspect
203 def inspect
200 puts '### Start Line Diff ###'
204 puts '### Start Line Diff ###'
201 puts self.nb_line_left
205 puts self.nb_line_left
202 puts self.line_left
206 puts self.line_left
203 puts self.nb_line_right
207 puts self.nb_line_right
204 puts self.line_right
208 puts self.line_right
205 end
209 end
206 end
210 end
207
211
208 class DiffTableList < Array
212 class DiffTableList < Array
209 def initialize (diff, type="inline")
213 def initialize (diff, type="inline")
210 diff_table = DiffTable.new type
214 diff_table = DiffTable.new type
211 diff.each do |line|
215 diff.each do |line|
212 if line =~ /^(Index:|diff) (.*)$/
216 if line =~ /^(---|\+\+\+) (.*)$/
213 self << diff_table if diff_table.length > 1
217 self << diff_table if diff_table.length > 1
214 diff_table = DiffTable.new type
218 diff_table = DiffTable.new type
215 end
219 end
216 a = diff_table.add_line line
220 a = diff_table.add_line line
217 end
221 end
218 self << diff_table
222 self << diff_table
219 end
223 end
220 end
224 end
221
225
222 # Class for create a Diff
226 # Class for create a Diff
223 class DiffTable < Hash
227 class DiffTable < Hash
224 attr_reader :file_name, :line_num_l, :line_num_r
228 attr_reader :file_name, :line_num_l, :line_num_r
225
229
226 # Initialize with a Diff file and the type of Diff View
230 # Initialize with a Diff file and the type of Diff View
227 # The type view must be inline or sbs (side_by_side)
231 # The type view must be inline or sbs (side_by_side)
228 def initialize (type="inline")
232 def initialize (type="inline")
229 @parsing = false
233 @parsing = false
230 @nb_line = 1
234 @nb_line = 1
231 @start = false
235 @start = false
232 @before = 'same'
236 @before = 'same'
233 @second = true
237 @second = true
234 @type = type
238 @type = type
235 end
239 end
236
240
237 # Function for add a line of this Diff
241 # Function for add a line of this Diff
238 def add_line(line)
242 def add_line(line)
239 unless @parsing
243 unless @parsing
240 if line =~ /^(Index:|diff) (.*)$/
244 if line =~ /^(---|\+\+\+) (.*)$/
241 @file_name = $2
245 @file_name = $2
242 return false
246 return false
243 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
247 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
244 @line_num_l = $5.to_i
248 @line_num_l = $5.to_i
245 @line_num_r = $2.to_i
249 @line_num_r = $2.to_i
246 @parsing = true
250 @parsing = true
247 end
251 end
248 else
252 else
249 if line =~ /^[^\+\-\s@\\]/
253 if line =~ /^[^\+\-\s@\\]/
250 self.delete(self.keys.sort.last)
254 self.delete(self.keys.sort.last)
251 @parsing = false
255 @parsing = false
252 return false
256 return false
253 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
257 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
254 @line_num_l = $5.to_i
258 @line_num_l = $5.to_i
255 @line_num_r = $2.to_i
259 @line_num_r = $2.to_i
256 else
260 else
257 @nb_line += 1 if parse_line(line, @type)
261 @nb_line += 1 if parse_line(line, @type)
258 end
262 end
259 end
263 end
260 return true
264 return true
261 end
265 end
262
266
263 def inspect
267 def inspect
264 puts '### DIFF TABLE ###'
268 puts '### DIFF TABLE ###'
265 puts "file : #{file_name}"
269 puts "file : #{file_name}"
266 self.each do |d|
270 self.each do |d|
267 d.inspect
271 d.inspect
268 end
272 end
269 end
273 end
270
274
271 private
275 private
272 # Test if is a Side By Side type
276 # Test if is a Side By Side type
273 def sbs?(type, func)
277 def sbs?(type, func)
274 if @start and type == "sbs"
278 if @start and type == "sbs"
275 if @before == func and @second
279 if @before == func and @second
276 tmp_nb_line = @nb_line
280 tmp_nb_line = @nb_line
277 self[tmp_nb_line] = Diff.new
281 self[tmp_nb_line] = Diff.new
278 else
282 else
279 @second = false
283 @second = false
280 tmp_nb_line = @start
284 tmp_nb_line = @start
281 @start += 1
285 @start += 1
282 @nb_line -= 1
286 @nb_line -= 1
283 end
287 end
284 else
288 else
285 tmp_nb_line = @nb_line
289 tmp_nb_line = @nb_line
286 @start = @nb_line
290 @start = @nb_line
287 self[tmp_nb_line] = Diff.new
291 self[tmp_nb_line] = Diff.new
288 @second = true
292 @second = true
289 end
293 end
290 unless self[tmp_nb_line]
294 unless self[tmp_nb_line]
291 @nb_line += 1
295 @nb_line += 1
292 self[tmp_nb_line] = Diff.new
296 self[tmp_nb_line] = Diff.new
293 else
297 else
294 self[tmp_nb_line]
298 self[tmp_nb_line]
295 end
299 end
296 end
300 end
297
301
298 # Escape the HTML for the diff
302 # Escape the HTML for the diff
299 def escapeHTML(line)
303 def escapeHTML(line)
300 CGI.escapeHTML(line).gsub(/\s/, '&nbsp;')
304 CGI.escapeHTML(line).gsub(/\s/, '&nbsp;')
301 end
305 end
302
306
303 def parse_line (line, type="inline")
307 def parse_line (line, type="inline")
304 if line[0, 1] == "+"
308 if line[0, 1] == "+"
305 diff = sbs? type, 'add'
309 diff = sbs? type, 'add'
306 @before = 'add'
310 @before = 'add'
307 diff.line_left = escapeHTML line[1..-1]
311 diff.line_left = escapeHTML line[1..-1]
308 diff.nb_line_left = @line_num_l
312 diff.nb_line_left = @line_num_l
309 diff.type_diff_left = 'diff_in'
313 diff.type_diff_left = 'diff_in'
310 @line_num_l += 1
314 @line_num_l += 1
311 true
315 true
312 elsif line[0, 1] == "-"
316 elsif line[0, 1] == "-"
313 diff = sbs? type, 'remove'
317 diff = sbs? type, 'remove'
314 @before = 'remove'
318 @before = 'remove'
315 diff.line_right = escapeHTML line[1..-1]
319 diff.line_right = escapeHTML line[1..-1]
316 diff.nb_line_right = @line_num_r
320 diff.nb_line_right = @line_num_r
317 diff.type_diff_right = 'diff_out'
321 diff.type_diff_right = 'diff_out'
318 @line_num_r += 1
322 @line_num_r += 1
319 true
323 true
320 elsif line[0, 1] =~ /\s/
324 elsif line[0, 1] =~ /\s/
321 @before = 'same'
325 @before = 'same'
322 @start = false
326 @start = false
323 diff = Diff.new
327 diff = Diff.new
324 diff.line_right = escapeHTML line[1..-1]
328 diff.line_right = escapeHTML line[1..-1]
325 diff.nb_line_right = @line_num_r
329 diff.nb_line_right = @line_num_r
326 diff.line_left = escapeHTML line[1..-1]
330 diff.line_left = escapeHTML line[1..-1]
327 diff.nb_line_left = @line_num_l
331 diff.nb_line_left = @line_num_l
328 self[@nb_line] = diff
332 self[@nb_line] = diff
329 @line_num_l += 1
333 @line_num_l += 1
330 @line_num_r += 1
334 @line_num_r += 1
331 true
335 true
332 elsif line[0, 1] = "\\"
336 elsif line[0, 1] = "\\"
333 true
337 true
334 else
338 else
335 false
339 false
336 end
340 end
337 end
341 end
338 end
342 end
339 end
343 end
340 end
344 end
341 end
345 end
General Comments 0
You need to be logged in to leave comments. Login now