annotate lib/redmine/scm/adapters/darcs_adapter.rb @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +0100
parents 513646585e45
children af80e5618e9b
rev   line source
Chris@0 1 # redMine - project management software
Chris@0 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 require 'redmine/scm/adapters/abstract_adapter'
Chris@0 19 require 'rexml/document'
Chris@0 20
Chris@0 21 module Redmine
Chris@0 22 module Scm
Chris@0 23 module Adapters
Chris@0 24 class DarcsAdapter < AbstractAdapter
Chris@0 25 # Darcs executable name
Chris@0 26 DARCS_BIN = "darcs"
Chris@0 27
Chris@0 28 class << self
Chris@0 29 def client_version
Chris@0 30 @@client_version ||= (darcs_binary_version || [])
Chris@0 31 end
Chris@0 32
Chris@0 33 def darcs_binary_version
Chris@0 34 cmd = "#{DARCS_BIN} --version"
Chris@0 35 version = nil
Chris@0 36 shellout(cmd) do |io|
Chris@0 37 # Read darcs version in first returned line
Chris@0 38 if m = io.gets.match(%r{((\d+\.)+\d+)})
Chris@0 39 version = m[0].scan(%r{\d+}).collect(&:to_i)
Chris@0 40 end
Chris@0 41 end
Chris@0 42 return nil if $? && $?.exitstatus != 0
Chris@0 43 version
Chris@0 44 end
Chris@0 45 end
Chris@0 46
Chris@0 47 def initialize(url, root_url=nil, login=nil, password=nil)
Chris@0 48 @url = url
Chris@0 49 @root_url = url
Chris@0 50 end
Chris@0 51
Chris@0 52 def supports_cat?
Chris@0 53 # cat supported in darcs 2.0.0 and higher
Chris@0 54 self.class.client_version_above?([2, 0, 0])
Chris@0 55 end
Chris@0 56
Chris@0 57 # Get info about the darcs repository
Chris@0 58 def info
Chris@0 59 rev = revisions(nil,nil,nil,{:limit => 1})
Chris@0 60 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
Chris@0 61 end
Chris@0 62
Chris@0 63 # Returns an Entries collection
Chris@0 64 # or nil if the given path doesn't exist in the repository
Chris@0 65 def entries(path=nil, identifier=nil)
Chris@0 66 path_prefix = (path.blank? ? '' : "#{path}/")
Chris@0 67 path = '.' if path.blank?
Chris@0 68 entries = Entries.new
Chris@0 69 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output"
Chris@0 70 cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
Chris@0 71 cmd << " #{shell_quote path}"
Chris@0 72 shellout(cmd) do |io|
Chris@0 73 begin
Chris@0 74 doc = REXML::Document.new(io)
Chris@0 75 if doc.root.name == 'directory'
Chris@0 76 doc.elements.each('directory/*') do |element|
Chris@0 77 next unless ['file', 'directory'].include? element.name
Chris@0 78 entries << entry_from_xml(element, path_prefix)
Chris@0 79 end
Chris@0 80 elsif doc.root.name == 'file'
Chris@0 81 entries << entry_from_xml(doc.root, path_prefix)
Chris@0 82 end
Chris@0 83 rescue
Chris@0 84 end
Chris@0 85 end
Chris@0 86 return nil if $? && $?.exitstatus != 0
Chris@0 87 entries.compact.sort_by_name
Chris@0 88 end
Chris@0 89
Chris@0 90 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
Chris@0 91 path = '.' if path.blank?
Chris@0 92 revisions = Revisions.new
Chris@0 93 cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
Chris@0 94 cmd << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from
Chris@0 95 cmd << " --last #{options[:limit].to_i}" if options[:limit]
Chris@0 96 shellout(cmd) do |io|
Chris@0 97 begin
Chris@0 98 doc = REXML::Document.new(io)
Chris@0 99 doc.elements.each("changelog/patch") do |patch|
Chris@0 100 message = patch.elements['name'].text
Chris@0 101 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
Chris@0 102 revisions << Revision.new({:identifier => nil,
Chris@0 103 :author => patch.attributes['author'],
Chris@0 104 :scmid => patch.attributes['hash'],
Chris@0 105 :time => Time.parse(patch.attributes['local_date']),
Chris@0 106 :message => message,
Chris@0 107 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
Chris@0 108 })
Chris@0 109 end
Chris@0 110 rescue
Chris@0 111 end
Chris@0 112 end
Chris@0 113 return nil if $? && $?.exitstatus != 0
Chris@0 114 revisions
Chris@0 115 end
Chris@0 116
Chris@0 117 def diff(path, identifier_from, identifier_to=nil)
Chris@0 118 path = '*' if path.blank?
Chris@0 119 cmd = "#{DARCS_BIN} diff --repodir #{@url}"
Chris@0 120 if identifier_to.nil?
Chris@0 121 cmd << " --match #{shell_quote("hash #{identifier_from}")}"
Chris@0 122 else
Chris@0 123 cmd << " --to-match #{shell_quote("hash #{identifier_from}")}"
Chris@0 124 cmd << " --from-match #{shell_quote("hash #{identifier_to}")}"
Chris@0 125 end
Chris@0 126 cmd << " -u #{shell_quote path}"
Chris@0 127 diff = []
Chris@0 128 shellout(cmd) do |io|
Chris@0 129 io.each_line do |line|
Chris@0 130 diff << line
Chris@0 131 end
Chris@0 132 end
Chris@0 133 return nil if $? && $?.exitstatus != 0
Chris@0 134 diff
Chris@0 135 end
Chris@0 136
Chris@0 137 def cat(path, identifier=nil)
Chris@0 138 cmd = "#{DARCS_BIN} show content --repodir #{@url}"
Chris@0 139 cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
Chris@0 140 cmd << " #{shell_quote path}"
Chris@0 141 cat = nil
Chris@0 142 shellout(cmd) do |io|
Chris@0 143 io.binmode
Chris@0 144 cat = io.read
Chris@0 145 end
Chris@0 146 return nil if $? && $?.exitstatus != 0
Chris@0 147 cat
Chris@0 148 end
Chris@0 149
Chris@0 150 private
Chris@0 151
Chris@0 152 # Returns an Entry from the given XML element
Chris@0 153 # or nil if the entry was deleted
Chris@0 154 def entry_from_xml(element, path_prefix)
Chris@0 155 modified_element = element.elements['modified']
Chris@0 156 if modified_element.elements['modified_how'].text.match(/removed/)
Chris@0 157 return nil
Chris@0 158 end
Chris@0 159
Chris@0 160 Entry.new({:name => element.attributes['name'],
Chris@0 161 :path => path_prefix + element.attributes['name'],
Chris@0 162 :kind => element.name == 'file' ? 'file' : 'dir',
Chris@0 163 :size => nil,
Chris@0 164 :lastrev => Revision.new({
Chris@0 165 :identifier => nil,
Chris@0 166 :scmid => modified_element.elements['patch'].attributes['hash']
Chris@0 167 })
Chris@0 168 })
Chris@0 169 end
Chris@0 170
Chris@0 171 # Retrieve changed paths for a single patch
Chris@0 172 def get_paths_for_patch(hash)
Chris@0 173 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
Chris@0 174 cmd << " --match #{shell_quote("hash #{hash}")} "
Chris@0 175 paths = []
Chris@0 176 shellout(cmd) do |io|
Chris@0 177 begin
Chris@0 178 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
Chris@0 179 # A root element is added so that REXML doesn't raise an error
Chris@0 180 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
Chris@0 181 doc.elements.each('fake_root/summary/*') do |modif|
Chris@0 182 paths << {:action => modif.name[0,1].upcase,
Chris@0 183 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
Chris@0 184 }
Chris@0 185 end
Chris@0 186 rescue
Chris@0 187 end
Chris@0 188 end
Chris@0 189 paths
Chris@0 190 rescue CommandFailed
Chris@0 191 paths
Chris@0 192 end
Chris@0 193 end
Chris@0 194 end
Chris@0 195 end
Chris@0 196 end