]> source.dussan.org Git - redmine.git/commitdiff
Added Darcs basic support.
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 24 Jun 2007 19:30:38 +0000 (19:30 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 24 Jun 2007 19:30:38 +0000 (19:30 +0000)
Files in the repository can not be viewed or downloaded since Darcs doesn't support cat-like command.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@573 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/helpers/repositories_helper.rb
app/models/changeset.rb
app/models/repository.rb
app/models/repository/darcs.rb [new file with mode: 0644]
app/views/repositories/changes.rhtml
lib/redmine.rb
lib/redmine/scm/adapters/abstract_adapter.rb
lib/redmine/scm/adapters/darcs_adapter.rb [new file with mode: 0644]

index e2058a71220060183d933086d0293850b75c57df..c2d56402939e0176268015d9f80906fef2c57787 100644 (file)
@@ -43,6 +43,10 @@ module RepositoriesHelper
       content_tag('p', form.password_field(:password, :size => 30))
   end
 
+  def darcs_field_tags(form, repository)
+      content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
+  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
index 2038266f9fe6352f0570d763aa1f6c5e6a9e2927..879896fe4a52c525283611dd1fac0ed969f01148 100644 (file)
@@ -23,6 +23,7 @@ class Changeset < ActiveRecord::Base
   validates_presence_of :repository_id, :revision, :committed_on, :commit_date
   validates_numericality_of :revision, :only_integer => true
   validates_uniqueness_of :revision, :scope => :repository_id
+  validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
   
   def committed_on=(date)
     self.commit_date = date
index 667ef5efc4d3fe2b4fe91a67b669081146e9fb0f..f77b51db250cc5b468ab4f4a83197e979a1ad395 100644 (file)
@@ -30,6 +30,10 @@ class Repository < ActiveRecord::Base
     self.class.scm_name
   end
   
+  def supports_cat?
+    scm.supports_cat?
+  end
+  
   def entries(path=nil, identifier=nil)
     scm.entries(path, identifier)
   end
diff --git a/app/models/repository/darcs.rb b/app/models/repository/darcs.rb
new file mode 100644 (file)
index 0000000..48cc246
--- /dev/null
@@ -0,0 +1,90 @@
+# 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/darcs_adapter'
+
+class Repository::Darcs < Repository
+  validates_presence_of :url
+
+  def scm_adapter
+    Redmine::Scm::Adapters::DarcsAdapter
+  end
+  
+  def self.scm_name
+    'Darcs'
+  end
+  
+  def entries(path=nil, identifier=nil)
+    entries=scm.entries(path, identifier)
+    if entries
+      entries.each do |entry|
+        # Search the DB for the entry's last change
+        changeset = changesets.find_by_scmid(entry.lastrev.scmid) if entry.lastrev && !entry.lastrev.scmid.blank?
+        if changeset
+          entry.lastrev.identifier = changeset.revision
+          entry.lastrev.name = changeset.revision
+          entry.lastrev.time = changeset.committed_on
+          entry.lastrev.author = changeset.committer
+        end
+      end
+    end
+    entries
+  end
+  
+  def diff(path, rev, rev_to, type)
+    patch_from = changesets.find_by_revision(rev)
+    patch_to = changesets.find_by_revision(rev_to) if rev_to
+    if path.blank?
+      path = patch_from.changes.collect{|change| change.path}.join(' ')
+    end
+    scm.diff(path, patch_from.scmid, patch_to.scmid, type)
+  end
+  
+  def fetch_changesets
+    scm_info = scm.info
+    if scm_info
+      db_last_id = latest_changeset ? latest_changeset.scmid : nil
+      next_rev = latest_changeset ? latest_changeset.revision + 1 : 1      
+      # latest revision in the repository
+      scm_revision = scm_info.lastrev.scmid      
+      unless changesets.find_by_scmid(scm_revision)
+        revisions = scm.revisions('', db_last_id, nil, :with_path => true)
+        transaction do
+          revisions.reverse_each do |revision|
+            changeset = Changeset.create(:repository => self,
+                                         :revision => next_rev,
+                                         :scmid => revision.scmid,
+                                         :committer => revision.author, 
+                                         :committed_on => revision.time,
+                                         :comments => revision.message)
+            
+            next if changeset.new_record?
+            
+            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
+            next_rev += 1
+          end if revisions
+        end
+      end
+    end
+  end
+end
index 35ce939fcc808999261b8ee9369a9ee46ea69723..b8610818b13ae4937c7362b3a6e558c12706384f 100644 (file)
@@ -2,6 +2,7 @@
 
 <h3><%=h @entry.name %></h3>
 
+<% if @repository.supports_cat? %>
 <p>
 <% if @entry.is_text? %>
 <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => @path, :rev => @rev } %> |
@@ -9,5 +10,6 @@
 <%= 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>
+<% end %>
 
 <%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changes, :entry => @entry }%>
index 76edeca55a8fcfcc2ccbf662fd1cbcdfa6090ecc..f6571589966085cf212e5eb4527d2e2d9c4018cd 100644 (file)
@@ -2,4 +2,4 @@ require 'redmine/version'
 require 'redmine/mime_type'
 require 'redmine/acts_as_watchable/init'
 
-REDMINE_SUPPORTED_SCM = %w( Subversion Mercurial Cvs )
+REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs )
index b74fa1b18b26948c388ea2b9083ab33902c9c283..16cbef6136c136e106522c7aa7c48cffdd04f33a 100644 (file)
@@ -35,6 +35,10 @@ module Redmine
           'Abstract'
         end
         
+        def supports_cat?
+          true
+        end
+        
         def root_url
           @root_url
         end
@@ -209,7 +213,7 @@ module Redmine
         def initialize (diff, type="inline")
             diff_table = DiffTable.new type
             diff.each do |line|
-                if line =~ /^(Index:|diff) (.*)$/
+                if line =~ /^(---|\+\+\+) (.*)$/
                     self << diff_table if diff_table.length > 1
                     diff_table = DiffTable.new type
                 end
@@ -237,7 +241,7 @@ module Redmine
         # Function for add a line of this Diff
         def add_line(line)
           unless @parsing
-            if line =~ /^(Index:|diff) (.*)$/
+            if line =~ /^(---|\+\+\+) (.*)$/
               @file_name = $2
               return false
             elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
diff --git a/lib/redmine/scm/adapters/darcs_adapter.rb b/lib/redmine/scm/adapters/darcs_adapter.rb
new file mode 100644 (file)
index 0000000..34b3620
--- /dev/null
@@ -0,0 +1,163 @@
+# 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 DarcsAdapter < AbstractAdapter      
+        # Darcs executable name
+        DARCS_BIN = "darcs"
+        
+        def initialize(url, root_url=nil, login=nil, password=nil)
+          @url = url
+          @root_url = url
+        end
+
+        def supports_cat?
+          false
+        end
+              
+        # Get info about the svn repository
+        def info
+          rev = revisions(nil,nil,nil,{:limit => 1})
+          rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : 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_prefix = (path.blank? ? '' : "#{path}/")
+          path = '.' if path.blank?
+          entries = Entries.new          
+          cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output #{path}"
+          shellout(cmd) do |io|
+            begin
+              doc = REXML::Document.new(io)
+              if doc.root.name == 'directory'
+                doc.elements.each('directory/*') do |element|
+                  next unless ['file', 'directory'].include? element.name
+                  entries << entry_from_xml(element, path_prefix)
+                end
+              elsif doc.root.name == 'file'
+                entries << entry_from_xml(doc.root, path_prefix)
+              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 = '.' if path.blank?
+          revisions = Revisions.new
+          cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
+          cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from
+          cmd << " --last #{options[:limit].to_i}" if options[:limit]
+          shellout(cmd) do |io|
+            begin
+              doc = REXML::Document.new(io)
+              doc.elements.each("changelog/patch") do |patch|
+                message = patch.elements['name'].text
+                message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
+                revisions << Revision.new({:identifier => nil,
+                              :author => patch.attributes['author'],
+                              :scmid => patch.attributes['hash'],
+                              :time => Time.parse(patch.attributes['local_date']),
+                              :message => message,
+                              :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
+                            })
+              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 path.blank?
+          cmd = "#{DARCS_BIN} diff --repodir #{@url}"
+          cmd << " --to-match \"hash #{identifier_from}\""
+          cmd << " --from-match \"hash #{identifier_to}\"" if identifier_to
+          cmd << " -u #{path}"
+          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
+        
+        private
+                
+        def entry_from_xml(element, path_prefix)
+          Entry.new({:name => element.attributes['name'],
+                     :path => path_prefix + element.attributes['name'],
+                     :kind => element.name == 'file' ? 'file' : 'dir',
+                     :size => nil,
+                     :lastrev => Revision.new({
+                       :identifier => nil,
+                       :scmid => element.elements['modified'].elements['patch'].attributes['hash']
+                       })
+                     })        
+        end
+        
+        # Retrieve changed paths for a single patch
+        def get_paths_for_patch(hash)
+          cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
+          cmd << " --match \"hash #{hash}\" "
+          paths = []
+          shellout(cmd) do |io|
+            begin
+              # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
+              # A root element is added so that REXML doesn't raise an error
+              doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
+              doc.elements.each('fake_root/summary/*') do |modif|
+                paths << {:action => modif.name[0,1].upcase,
+                          :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
+                         }
+              end
+            rescue
+            end
+          end
+          paths
+        rescue Errno::ENOENT => e
+          paths
+        end
+      end
+    end
+  end
+end