To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / lib / redmine / scm / adapters / darcs_adapter.rb @ 912:5e80956cc792

History | View | Annotate | Download (8.72 KB)

1 441:cbce1fd3b1b7 Chris
# Redmine - project management software
2
# Copyright (C) 2006-2011  Jean-Philippe Lang
3 0:513646585e45 Chris
#
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 441:cbce1fd3b1b7 Chris
#
9 0:513646585e45 Chris
# 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 441:cbce1fd3b1b7 Chris
#
14 0:513646585e45 Chris
# 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 245:051f544170fe Chris
    module Adapters
24
      class DarcsAdapter < AbstractAdapter
25 0:513646585e45 Chris
        # Darcs executable name
26 210:0579821a129a Chris
        DARCS_BIN = Redmine::Configuration['scm_darcs_command'] || "darcs"
27 245:051f544170fe Chris
28 0:513646585e45 Chris
        class << self
29 245:051f544170fe Chris
          def client_command
30
            @@bin    ||= DARCS_BIN
31
          end
32
33
          def sq_bin
34 909:cbb26bc654de Chris
            @@sq_bin ||= shell_quote_command
35 245:051f544170fe Chris
          end
36
37 0:513646585e45 Chris
          def client_version
38
            @@client_version ||= (darcs_binary_version || [])
39
          end
40 245:051f544170fe Chris
41
          def client_available
42
            !client_version.empty?
43
          end
44
45 0:513646585e45 Chris
          def darcs_binary_version
46 245:051f544170fe Chris
            darcsversion = darcs_binary_version_from_command_line.dup
47
            if darcsversion.respond_to?(:force_encoding)
48
              darcsversion.force_encoding('ASCII-8BIT')
49
            end
50 210:0579821a129a Chris
            if m = darcsversion.match(%r{\A(.*?)((\d+\.)+\d+)})
51
              m[2].scan(%r{\d+}).collect(&:to_i)
52 0:513646585e45 Chris
            end
53 210:0579821a129a Chris
          end
54
55
          def darcs_binary_version_from_command_line
56 245:051f544170fe Chris
            shellout("#{sq_bin} --version") { |io| io.read }.to_s
57 0:513646585e45 Chris
          end
58
        end
59
60 245:051f544170fe Chris
        def initialize(url, root_url=nil, login=nil, password=nil,
61
                       path_encoding=nil)
62 0:513646585e45 Chris
          @url = url
63
          @root_url = url
64
        end
65
66
        def supports_cat?
67
          # cat supported in darcs 2.0.0 and higher
68
          self.class.client_version_above?([2, 0, 0])
69
        end
70
71
        # Get info about the darcs repository
72
        def info
73
          rev = revisions(nil,nil,nil,{:limit => 1})
74
          rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
75
        end
76 245:051f544170fe Chris
77 0:513646585e45 Chris
        # Returns an Entries collection
78
        # or nil if the given path doesn't exist in the repository
79 441:cbce1fd3b1b7 Chris
        def entries(path=nil, identifier=nil, options={})
80 0:513646585e45 Chris
          path_prefix = (path.blank? ? '' : "#{path}/")
81 210:0579821a129a Chris
          if path.blank?
82
            path = ( self.class.client_version_above?([2, 2, 0]) ? @url : '.' )
83
          end
84 245:051f544170fe Chris
          entries = Entries.new
85
          cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --xml-output"
86 0:513646585e45 Chris
          cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
87
          cmd << " #{shell_quote path}"
88
          shellout(cmd) do |io|
89
            begin
90
              doc = REXML::Document.new(io)
91
              if doc.root.name == 'directory'
92
                doc.elements.each('directory/*') do |element|
93
                  next unless ['file', 'directory'].include? element.name
94
                  entries << entry_from_xml(element, path_prefix)
95
                end
96
              elsif doc.root.name == 'file'
97
                entries << entry_from_xml(doc.root, path_prefix)
98
              end
99
            rescue
100
            end
101
          end
102
          return nil if $? && $?.exitstatus != 0
103
          entries.compact.sort_by_name
104
        end
105 245:051f544170fe Chris
106 0:513646585e45 Chris
        def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
107
          path = '.' if path.blank?
108
          revisions = Revisions.new
109 245:051f544170fe Chris
          cmd = "#{self.class.sq_bin} changes --repodir #{shell_quote @url} --xml-output"
110 0:513646585e45 Chris
          cmd << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from
111
          cmd << " --last #{options[:limit].to_i}" if options[:limit]
112
          shellout(cmd) do |io|
113
            begin
114
              doc = REXML::Document.new(io)
115
              doc.elements.each("changelog/patch") do |patch|
116
                message = patch.elements['name'].text
117
                message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
118
                revisions << Revision.new({:identifier => nil,
119
                              :author => patch.attributes['author'],
120
                              :scmid => patch.attributes['hash'],
121
                              :time => Time.parse(patch.attributes['local_date']),
122
                              :message => message,
123
                              :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
124
                            })
125
              end
126
            rescue
127
            end
128
          end
129
          return nil if $? && $?.exitstatus != 0
130
          revisions
131
        end
132 245:051f544170fe Chris
133 0:513646585e45 Chris
        def diff(path, identifier_from, identifier_to=nil)
134
          path = '*' if path.blank?
135 245:051f544170fe Chris
          cmd = "#{self.class.sq_bin} diff --repodir #{shell_quote @url}"
136 0:513646585e45 Chris
          if identifier_to.nil?
137
            cmd << " --match #{shell_quote("hash #{identifier_from}")}"
138
          else
139
            cmd << " --to-match #{shell_quote("hash #{identifier_from}")}"
140
            cmd << " --from-match #{shell_quote("hash #{identifier_to}")}"
141
          end
142
          cmd << " -u #{shell_quote path}"
143
          diff = []
144
          shellout(cmd) do |io|
145
            io.each_line do |line|
146
              diff << line
147
            end
148
          end
149
          return nil if $? && $?.exitstatus != 0
150
          diff
151
        end
152 245:051f544170fe Chris
153 0:513646585e45 Chris
        def cat(path, identifier=nil)
154 245:051f544170fe Chris
          cmd = "#{self.class.sq_bin} show content --repodir #{shell_quote @url}"
155 0:513646585e45 Chris
          cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
156
          cmd << " #{shell_quote path}"
157
          cat = nil
158
          shellout(cmd) do |io|
159
            io.binmode
160
            cat = io.read
161
          end
162
          return nil if $? && $?.exitstatus != 0
163
          cat
164
        end
165
166
        private
167 245:051f544170fe Chris
168 0:513646585e45 Chris
        # Returns an Entry from the given XML element
169
        # or nil if the entry was deleted
170
        def entry_from_xml(element, path_prefix)
171
          modified_element = element.elements['modified']
172
          if modified_element.elements['modified_how'].text.match(/removed/)
173
            return nil
174
          end
175 245:051f544170fe Chris
176 0:513646585e45 Chris
          Entry.new({:name => element.attributes['name'],
177
                     :path => path_prefix + element.attributes['name'],
178
                     :kind => element.name == 'file' ? 'file' : 'dir',
179
                     :size => nil,
180
                     :lastrev => Revision.new({
181
                       :identifier => nil,
182
                       :scmid => modified_element.elements['patch'].attributes['hash']
183
                       })
184 245:051f544170fe Chris
                     })
185 0:513646585e45 Chris
        end
186 210:0579821a129a Chris
187
        def get_paths_for_patch(hash)
188
          paths = get_paths_for_patch_raw(hash)
189
          if self.class.client_version_above?([2, 4])
190
            orig_paths = paths
191
            paths = []
192
            add_paths = []
193
            add_paths_name = []
194
            mod_paths = []
195
            other_paths = []
196
            orig_paths.each do |path|
197
              if path[:action] == 'A'
198
                add_paths << path
199
                add_paths_name << path[:path]
200
              elsif path[:action] == 'M'
201
                mod_paths << path
202
              else
203
                other_paths << path
204
              end
205
            end
206
            add_paths_name.each do |add_path|
207
              mod_paths.delete_if { |m| m[:path] == add_path }
208
            end
209
            paths.concat add_paths
210
            paths.concat mod_paths
211
            paths.concat other_paths
212
          end
213
          paths
214
        end
215 245:051f544170fe Chris
216 0:513646585e45 Chris
        # Retrieve changed paths for a single patch
217 210:0579821a129a Chris
        def get_paths_for_patch_raw(hash)
218 245:051f544170fe Chris
          cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --summary --xml-output"
219 0:513646585e45 Chris
          cmd << " --match #{shell_quote("hash #{hash}")} "
220
          paths = []
221
          shellout(cmd) do |io|
222
            begin
223
              # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
224
              # A root element is added so that REXML doesn't raise an error
225
              doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
226
              doc.elements.each('fake_root/summary/*') do |modif|
227
                paths << {:action => modif.name[0,1].upcase,
228
                          :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
229
                         }
230
              end
231
            rescue
232
            end
233
          end
234
          paths
235
        rescue CommandFailed
236
          paths
237
        end
238
      end
239
    end
240
  end
241
end