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 / .svn / pristine / 54 / 54bd7e60efd3a104b0657fe44ac9bc64d28fe9fb.svn-base @ 1297:0a574315af3e

History | View | Annotate | Download (8.74 KB)

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