| 
									
										
										
										
											2011-05-06 12:57:54 +00:00
										 |  |  | # Redmine - project management software | 
					
						
							| 
									
										
										
										
											2015-01-11 09:09:50 +00:00
										 |  |  | # Copyright (C) 2006-2015  Jean-Philippe Lang | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  | # | 
					
						
							|  |  |  | # 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. | 
					
						
							| 
									
										
										
										
											2011-05-06 12:57:54 +00:00
										 |  |  | # | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  | # 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. | 
					
						
							| 
									
										
										
										
											2011-05-06 12:57:54 +00:00
										 |  |  | # | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  | # 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 | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  |     module Adapters | 
					
						
							| 
									
										
										
										
											2011-02-19 00:56:13 +00:00
										 |  |  |       class DarcsAdapter < AbstractAdapter | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |         # Darcs executable name | 
					
						
							| 
									
										
										
										
											2011-02-04 10:24:10 +00:00
										 |  |  |         DARCS_BIN = Redmine::Configuration['scm_darcs_command'] || "darcs" | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-08-25 14:33:30 +00:00
										 |  |  |         class << self | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  |           def client_command | 
					
						
							|  |  |  |             @@bin    ||= DARCS_BIN | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           def sq_bin | 
					
						
							| 
									
										
										
										
											2011-07-15 16:29:40 +00:00
										 |  |  |             @@sq_bin ||= shell_quote_command | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-08-25 14:33:30 +00:00
										 |  |  |           def client_version | 
					
						
							|  |  |  |             @@client_version ||= (darcs_binary_version || []) | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-02-19 01:11:30 +00:00
										 |  |  |           def client_available | 
					
						
							|  |  |  |             !client_version.empty? | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-08-25 14:33:30 +00:00
										 |  |  |           def darcs_binary_version | 
					
						
							| 
									
										
										
										
											2014-10-22 17:37:16 +00:00
										 |  |  |             darcsversion = darcs_binary_version_from_command_line.dup.force_encoding('ASCII-8BIT') | 
					
						
							| 
									
										
										
										
											2011-01-22 15:38:23 +00:00
										 |  |  |             if m = darcsversion.match(%r{\A(.*?)((\d+\.)+\d+)}) | 
					
						
							|  |  |  |               m[2].scan(%r{\d+}).collect(&:to_i) | 
					
						
							| 
									
										
										
										
											2008-08-25 14:33:30 +00:00
										 |  |  |             end | 
					
						
							| 
									
										
										
										
											2011-01-22 15:38:23 +00:00
										 |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           def darcs_binary_version_from_command_line | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  |             shellout("#{sq_bin} --version") { |io| io.read }.to_s | 
					
						
							| 
									
										
										
										
											2008-08-25 14:33:30 +00:00
										 |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-02-24 05:58:59 +00:00
										 |  |  |         def initialize(url, root_url=nil, login=nil, password=nil, | 
					
						
							|  |  |  |                        path_encoding=nil) | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |           @url = url | 
					
						
							|  |  |  |           @root_url = url | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def supports_cat? | 
					
						
							| 
									
										
										
										
											2008-08-25 14:33:30 +00:00
										 |  |  |           # cat supported in darcs 2.0.0 and higher | 
					
						
							|  |  |  |           self.class.client_version_above?([2, 0, 0]) | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2008-08-25 14:33:30 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Get info about the darcs repository | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |         def info | 
					
						
							|  |  |  |           rev = revisions(nil,nil,nil,{:limit => 1}) | 
					
						
							|  |  |  |           rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |         # Returns an Entries collection | 
					
						
							|  |  |  |         # or nil if the given path doesn't exist in the repository | 
					
						
							| 
									
										
										
										
											2011-05-04 14:11:25 +00:00
										 |  |  |         def entries(path=nil, identifier=nil, options={}) | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |           path_prefix = (path.blank? ? '' : "#{path}/") | 
					
						
							| 
									
										
										
										
											2011-01-22 15:38:47 +00:00
										 |  |  |           if path.blank? | 
					
						
							|  |  |  |             path = ( self.class.client_version_above?([2, 2, 0]) ? @url : '.' ) | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2011-02-19 00:56:13 +00:00
										 |  |  |           entries = Entries.new | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  |           cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --xml-output" | 
					
						
							| 
									
										
										
										
											2009-01-04 13:27:48 +00:00
										 |  |  |           cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier | 
					
						
							|  |  |  |           cmd << " #{shell_quote path}" | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |           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
 | 
					
						
							| 
									
										
										
										
											2012-03-02 08:40:41 +00:00
										 |  |  |           entries.compact! | 
					
						
							|  |  |  |           entries.sort_by_name | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |         def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) | 
					
						
							|  |  |  |           path = '.' if path.blank? | 
					
						
							|  |  |  |           revisions = Revisions.new | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  |           cmd = "#{self.class.sq_bin} changes --repodir #{shell_quote @url} --xml-output" | 
					
						
							| 
									
										
										
										
											2009-01-04 13:27:48 +00:00
										 |  |  |           cmd << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |           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 | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |         def diff(path, identifier_from, identifier_to=nil) | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |           path = '*' if path.blank? | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  |           cmd = "#{self.class.sq_bin} diff --repodir #{shell_quote @url}" | 
					
						
							| 
									
										
										
										
											2008-03-12 20:28:49 +00:00
										 |  |  |           if identifier_to.nil? | 
					
						
							| 
									
										
										
										
											2009-01-04 13:27:48 +00:00
										 |  |  |             cmd << " --match #{shell_quote("hash #{identifier_from}")}" | 
					
						
							| 
									
										
										
										
											2008-03-12 20:28:49 +00:00
										 |  |  |           else | 
					
						
							| 
									
										
										
										
											2009-01-04 13:27:48 +00:00
										 |  |  |             cmd << " --to-match #{shell_quote("hash #{identifier_from}")}" | 
					
						
							|  |  |  |             cmd << " --from-match #{shell_quote("hash #{identifier_to}")}" | 
					
						
							| 
									
										
										
										
											2008-03-12 20:28:49 +00:00
										 |  |  |           end | 
					
						
							| 
									
										
										
										
											2009-01-04 13:27:48 +00:00
										 |  |  |           cmd << " -u #{shell_quote path}" | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |           diff = [] | 
					
						
							|  |  |  |           shellout(cmd) do |io| | 
					
						
							|  |  |  |             io.each_line do |line| | 
					
						
							|  |  |  |               diff << line | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |           return nil if $? && $?.exitstatus != 0
 | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |           diff | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-08-25 14:33:30 +00:00
										 |  |  |         def cat(path, identifier=nil) | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  |           cmd = "#{self.class.sq_bin} show content --repodir #{shell_quote @url}" | 
					
						
							| 
									
										
										
										
											2009-01-04 13:27:48 +00:00
										 |  |  |           cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier | 
					
						
							| 
									
										
										
										
											2008-08-25 14:33:30 +00:00
										 |  |  |           cmd << " #{shell_quote path}" | 
					
						
							|  |  |  |           cat = nil | 
					
						
							|  |  |  |           shellout(cmd) do |io| | 
					
						
							|  |  |  |             io.binmode | 
					
						
							|  |  |  |             cat = io.read | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |           return nil if $? && $?.exitstatus != 0
 | 
					
						
							|  |  |  |           cat | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |         private | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-12-27 18:33:35 +00:00
										 |  |  |         # Returns an Entry from the given XML element | 
					
						
							|  |  |  |         # or nil if the entry was deleted | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |         def entry_from_xml(element, path_prefix) | 
					
						
							| 
									
										
										
										
											2008-12-27 18:33:35 +00:00
										 |  |  |           modified_element = element.elements['modified'] | 
					
						
							|  |  |  |           if modified_element.elements['modified_how'].text.match(/removed/) | 
					
						
							|  |  |  |             return nil | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2011-02-19 00:56:13 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |           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, | 
					
						
							| 
									
										
										
										
											2008-12-27 18:33:35 +00:00
										 |  |  |                        :scmid => modified_element.elements['patch'].attributes['hash'] | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |                        }) | 
					
						
							| 
									
										
										
										
											2011-02-19 00:56:13 +00:00
										 |  |  |                      }) | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2011-01-23 04:17:26 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def get_paths_for_patch(hash) | 
					
						
							|  |  |  |           paths = get_paths_for_patch_raw(hash) | 
					
						
							|  |  |  |           if self.class.client_version_above?([2, 4]) | 
					
						
							|  |  |  |             orig_paths = paths | 
					
						
							|  |  |  |             paths = [] | 
					
						
							|  |  |  |             add_paths = [] | 
					
						
							|  |  |  |             add_paths_name = [] | 
					
						
							|  |  |  |             mod_paths = [] | 
					
						
							|  |  |  |             other_paths = [] | 
					
						
							|  |  |  |             orig_paths.each do |path| | 
					
						
							|  |  |  |               if path[:action] == 'A' | 
					
						
							|  |  |  |                 add_paths << path | 
					
						
							|  |  |  |                 add_paths_name << path[:path] | 
					
						
							|  |  |  |               elsif path[:action] == 'M' | 
					
						
							|  |  |  |                 mod_paths << path | 
					
						
							|  |  |  |               else | 
					
						
							|  |  |  |                 other_paths << path | 
					
						
							|  |  |  |               end | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |             add_paths_name.each do |add_path| | 
					
						
							|  |  |  |               mod_paths.delete_if { |m| m[:path] == add_path } | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |             paths.concat add_paths | 
					
						
							|  |  |  |             paths.concat mod_paths | 
					
						
							|  |  |  |             paths.concat other_paths | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |           paths | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |         # Retrieve changed paths for a single patch | 
					
						
							| 
									
										
										
										
											2011-01-23 04:17:26 +00:00
										 |  |  |         def get_paths_for_patch_raw(hash) | 
					
						
							| 
									
										
										
										
											2011-02-14 06:14:34 +00:00
										 |  |  |           cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --summary --xml-output" | 
					
						
							| 
									
										
										
										
											2009-01-04 13:27:48 +00:00
										 |  |  |           cmd << " --match #{shell_quote("hash #{hash}")} " | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |           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 | 
					
						
							| 
									
										
										
										
											2007-12-15 12:14:40 +00:00
										 |  |  |         rescue CommandFailed | 
					
						
							| 
									
										
										
										
											2007-06-24 19:30:38 +00:00
										 |  |  |           paths | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |