comparison lib/redmine/scm/adapters/mercurial_adapter.rb @ 119:8661b858af72

* Update to Redmine trunk rev 4705
author Chris Cannam
date Thu, 13 Jan 2011 14:12:06 +0000
parents 513646585e45
children cd2282d2aa55 0579821a129a
comparison
equal deleted inserted replaced
39:150ceac17a8d 119:8661b858af72
14 # You should have received a copy of the GNU General Public License 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 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. 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 require 'redmine/scm/adapters/abstract_adapter' 18 require 'redmine/scm/adapters/abstract_adapter'
19 require 'cgi'
19 20
20 module Redmine 21 module Redmine
21 module Scm 22 module Scm
22 module Adapters 23 module Adapters
23 class MercurialAdapter < AbstractAdapter 24 class MercurialAdapter < AbstractAdapter
24 25
25 # Mercurial executable name 26 # Mercurial executable name
26 HG_BIN = "hg" 27 HG_BIN = "hg"
27 TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial" 28 TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial"
28 TEMPLATE_NAME = "hg-template" 29 TEMPLATE_NAME = "hg-template"
29 TEMPLATE_EXTENSION = "tmpl" 30 TEMPLATE_EXTENSION = "tmpl"
30 31
31 class << self 32 class << self
32 def client_version 33 def client_version
33 @@client_version ||= (hgversion || []) 34 @@client_version ||= (hgversion || [])
34 end 35 end
35 36
36 def hgversion 37 def hgversion
37 # The hg version is expressed either as a 38 # The hg version is expressed either as a
38 # release number (eg 0.9.5 or 1.0) or as a revision 39 # release number (eg 0.9.5 or 1.0) or as a revision
39 # id composed of 12 hexa characters. 40 # id composed of 12 hexa characters.
40 theversion = hgversion_from_command_line 41 theversion = hgversion_from_command_line
41 if theversion.match(/^\d+(\.\d+)+/) 42 if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
42 theversion.split(".").collect(&:to_i) 43 m[2].scan(%r{\d+}).collect(&:to_i)
43 end 44 end
44 end 45 end
45 46
46 def hgversion_from_command_line 47 def hgversion_from_command_line
47 %x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1] 48 shellout("#{HG_BIN} --version") { |io| io.read }.to_s
48 end 49 end
49 50
50 def template_path 51 def template_path
51 @@template_path ||= template_path_for(client_version) 52 @@template_path ||= template_path_for(client_version)
52 end 53 end
53 54
54 def template_path_for(version) 55 def template_path_for(version)
55 if ((version <=> [0,9,5]) > 0) || version.empty? 56 if ((version <=> [0,9,5]) > 0) || version.empty?
56 ver = "1.0" 57 ver = "1.0"
57 else 58 else
58 ver = "0.9.5" 59 ver = "0.9.5"
59 end 60 end
60 "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" 61 "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
61 end 62 end
62 end 63 end
63 64
64 def info 65 def info
65 cmd = "#{HG_BIN} -R #{target('')} root" 66 cmd = "#{HG_BIN} -R #{target('')} root"
66 root_url = nil 67 root_url = nil
67 shellout(cmd) do |io| 68 shellout(cmd) do |io|
68 root_url = io.read 69 root_url = io.read
73 }) 74 })
74 info 75 info
75 rescue CommandFailed 76 rescue CommandFailed
76 return nil 77 return nil
77 end 78 end
78 79
79 def entries(path=nil, identifier=nil) 80 def entries(path=nil, identifier=nil)
80 path ||= '' 81 path ||= ''
81 entries = Entries.new 82 entries = Entries.new
82 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate" 83 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
83 cmd << " -r " + (identifier ? identifier.to_s : "tip") 84 cmd << " -r #{hgrev(identifier)}"
84 cmd << " " + shell_quote("path:#{path}") unless path.empty? 85 cmd << " " + shell_quote("path:#{path}") unless path.empty?
85 shellout(cmd) do |io| 86 shellout(cmd) do |io|
86 io.each_line do |line| 87 io.each_line do |line|
87 # HG uses antislashs as separator on Windows 88 # HG uses antislashs as separator on Windows
88 line = line.gsub(/\\/, "/") 89 line = line.gsub(/\\/, "/")
98 end 99 end
99 end 100 end
100 return nil if $? && $?.exitstatus != 0 101 return nil if $? && $?.exitstatus != 0
101 entries.sort_by_name 102 entries.sort_by_name
102 end 103 end
103 104
104 # Fetch the revisions by using a template file that 105 # Fetch the revisions by using a template file that
105 # makes Mercurial produce a xml output. 106 # makes Mercurial produce a xml output.
106 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) 107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
107 revisions = Revisions.new 108 revisions = Revisions.new
108 cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}" 109 cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}"
109 if identifier_from && identifier_to 110 if identifier_from && identifier_to
110 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}" 111 cmd << " -r #{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
111 elsif identifier_from 112 elsif identifier_from
112 cmd << " -r #{identifier_from.to_i}:" 113 cmd << " -r #{hgrev(identifier_from)}:"
113 end 114 end
114 cmd << " --limit #{options[:limit].to_i}" if options[:limit] 115 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
115 cmd << " #{path}" if path 116 cmd << " #{shell_quote path}" unless path.blank?
116 shellout(cmd) do |io| 117 shellout(cmd) do |io|
117 begin 118 begin
118 # HG doesn't close the XML Document... 119 # HG doesn't close the XML Document...
119 doc = REXML::Document.new(io.read << "</log>") 120 doc = REXML::Document.new(io.read << "</log>")
120 doc.elements.each("log/logentry") do |logentry| 121 doc.elements.each("log/logentry") do |logentry|
125 if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text } 126 if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text }
126 from_path = c.attributes['copyfrom-path'] 127 from_path = c.attributes['copyfrom-path']
127 from_rev = logentry.attributes['revision'] 128 from_rev = logentry.attributes['revision']
128 end 129 end
129 paths << {:action => path.attributes['action'], 130 paths << {:action => path.attributes['action'],
130 :path => "/#{path.text}", 131 :path => "/#{CGI.unescape(path.text)}",
131 :from_path => from_path ? "/#{from_path}" : nil, 132 :from_path => from_path ? "/#{CGI.unescape(from_path)}" : nil,
132 :from_revision => from_rev ? from_rev : nil 133 :from_revision => from_rev ? from_rev : nil
133 } 134 }
134 end 135 end
135 paths.sort! { |x,y| x[:path] <=> y[:path] } 136 paths.sort! { |x,y| x[:path] <=> y[:path] }
136 137
137 revisions << Revision.new({:identifier => logentry.attributes['revision'], 138 revisions << Revision.new({:identifier => logentry.attributes['revision'],
138 :scmid => logentry.attributes['node'], 139 :scmid => logentry.attributes['node'],
139 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), 140 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
140 :time => Time.parse(logentry.elements['date'].text).localtime, 141 :time => Time.parse(logentry.elements['date'].text).localtime,
141 :message => logentry.elements['msg'].text, 142 :message => logentry.elements['msg'].text,
147 end 148 end
148 end 149 end
149 return nil if $? && $?.exitstatus != 0 150 return nil if $? && $?.exitstatus != 0
150 revisions 151 revisions
151 end 152 end
152 153
153 def diff(path, identifier_from, identifier_to=nil) 154 def diff(path, identifier_from, identifier_to=nil)
154 path ||= '' 155 path ||= ''
156 diff_args = ''
157 diff = []
155 if identifier_to 158 if identifier_to
156 identifier_to = identifier_to.to_i 159 diff_args = "-r #{hgrev(identifier_to)} -r #{hgrev(identifier_from)}"
157 else 160 else
158 identifier_to = identifier_from.to_i - 1 161 if self.class.client_version_above?([1, 2])
159 end 162 diff_args = "-c #{hgrev(identifier_from)}"
160 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates" 163 else
164 return []
165 end
166 end
167 cmd = "#{HG_BIN} -R #{target('')} --config diff.git=false diff --nodates #{diff_args}"
161 cmd << " -I #{target(path)}" unless path.empty? 168 cmd << " -I #{target(path)}" unless path.empty?
162 diff = []
163 shellout(cmd) do |io| 169 shellout(cmd) do |io|
164 io.each_line do |line| 170 io.each_line do |line|
165 diff << line 171 diff << line
166 end 172 end
167 end 173 end
168 return nil if $? && $?.exitstatus != 0 174 return nil if $? && $?.exitstatus != 0
169 diff 175 diff
170 end 176 end
171 177
172 def cat(path, identifier=nil) 178 def cat(path, identifier=nil)
173 cmd = "#{HG_BIN} -R #{target('')} cat" 179 cmd = "#{HG_BIN} -R #{target('')} cat"
174 cmd << " -r " + (identifier ? identifier.to_s : "tip") 180 cmd << " -r #{hgrev(identifier)}"
175 cmd << " #{target(path)}" 181 cmd << " #{target(path)}"
176 cat = nil 182 cat = nil
177 shellout(cmd) do |io| 183 shellout(cmd) do |io|
178 io.binmode 184 io.binmode
179 cat = io.read 185 cat = io.read
180 end 186 end
181 return nil if $? && $?.exitstatus != 0 187 return nil if $? && $?.exitstatus != 0
182 cat 188 cat
183 end 189 end
184 190
185 def annotate(path, identifier=nil) 191 def annotate(path, identifier=nil)
186 path ||= '' 192 path ||= ''
187 cmd = "#{HG_BIN} -R #{target('')}" 193 cmd = "#{HG_BIN} -R #{target('')}"
188 cmd << " annotate -n -u" 194 cmd << " annotate -ncu"
189 cmd << " -r " + (identifier ? identifier.to_s : "tip") 195 cmd << " -r #{hgrev(identifier)}"
190 cmd << " -r #{identifier.to_i}" if identifier
191 cmd << " #{target(path)}" 196 cmd << " #{target(path)}"
192 blame = Annotate.new 197 blame = Annotate.new
193 shellout(cmd) do |io| 198 shellout(cmd) do |io|
194 io.each_line do |line| 199 io.each_line do |line|
195 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$} 200 next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$}
196 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip)) 201 r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3,
202 :identifier => $3)
203 blame.add_line($4.rstrip, r)
197 end 204 end
198 end 205 end
199 return nil if $? && $?.exitstatus != 0 206 return nil if $? && $?.exitstatus != 0
200 blame 207 blame
201 end 208 end
209
210 class Revision < Redmine::Scm::Adapters::Revision
211 # Returns the readable identifier
212 def format_identifier
213 "#{revision}:#{scmid}"
214 end
215 end
216
217 # Returns correct revision identifier
218 def hgrev(identifier)
219 shell_quote(identifier.blank? ? 'tip' : identifier.to_s)
220 end
221 private :hgrev
202 end 222 end
203 end 223 end
204 end 224 end
205 end 225 end