comparison lib/redmine/scm/adapters/mercurial_adapter.rb @ 120:cd2282d2aa55 cannam

Merge from the default branch. Note that this is not a valid SVN repository any more (use default, redmine-1.1 etc for SVN updates).
author Chris Cannam
date Thu, 13 Jan 2011 14:33:08 +0000
parents b859cc0c4fa1 8661b858af72
children eeebe205a056
comparison
equal deleted inserted replaced
118:b859cc0c4fa1 120:cd2282d2aa55
20 20
21 module Redmine 21 module Redmine
22 module Scm 22 module Scm
23 module Adapters 23 module Adapters
24 class MercurialAdapter < AbstractAdapter 24 class MercurialAdapter < AbstractAdapter
25 25
26 # Mercurial executable name 26 # Mercurial executable name
27 HG_BIN = "hg" 27 HG_BIN = "hg"
28 TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial" 28 TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial"
29 TEMPLATE_NAME = "hg-template" 29 TEMPLATE_NAME = "hg-template"
30 TEMPLATE_EXTENSION = "tmpl" 30 TEMPLATE_EXTENSION = "tmpl"
31 31
32 class << self 32 class << self
33 def client_version 33 def client_version
34 @@client_version ||= (hgversion || []) 34 @@client_version ||= (hgversion || [])
35 end 35 end
36 36
37 def hgversion 37 def hgversion
38 # The hg version is expressed either as a 38 # The hg version is expressed either as a
39 # 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
40 # id composed of 12 hexa characters. 40 # id composed of 12 hexa characters.
41 theversion = hgversion_from_command_line 41 theversion = hgversion_from_command_line
42 if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)}) 42 if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
43 m[2].scan(%r{\d+}).collect(&:to_i) 43 m[2].scan(%r{\d+}).collect(&:to_i)
44 end 44 end
45 end 45 end
46 46
47 def hgversion_from_command_line 47 def hgversion_from_command_line
48 shellout("#{HG_BIN} --version") { |io| io.read }.to_s 48 shellout("#{HG_BIN} --version") { |io| io.read }.to_s
49 end 49 end
50 50
51 def template_path 51 def template_path
52 @@template_path ||= template_path_for(client_version) 52 @@template_path ||= template_path_for(client_version)
53 end 53 end
54 54
55 def template_path_for(version) 55 def template_path_for(version)
56 if ((version <=> [0,9,5]) > 0) || version.empty? 56 if ((version <=> [0,9,5]) > 0) || version.empty?
57 ver = "1.0" 57 ver = "1.0"
58 else 58 else
59 ver = "0.9.5" 59 ver = "0.9.5"
60 end 60 end
61 "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" 61 "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
62 end 62 end
63 end 63 end
64 64
65 def info 65 def info
66 cmd = "#{HG_BIN} -R #{target('')} root" 66 cmd = "#{HG_BIN} -R #{target('')} root"
67 root_url = nil 67 root_url = nil
68 shellout(cmd) do |io| 68 shellout(cmd) do |io|
69 root_url = io.read 69 root_url = io.read
74 }) 74 })
75 info 75 info
76 rescue CommandFailed 76 rescue CommandFailed
77 return nil 77 return nil
78 end 78 end
79 79
80 def entries(path=nil, identifier=nil) 80 def entries(path=nil, identifier=nil)
81 path ||= '' 81 path ||= ''
82 entries = Entries.new 82 entries = Entries.new
83 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate" 83 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
84 cmd << " -r " + shell_quote(identifier ? identifier.to_s : "tip") 84 cmd << " -r #{hgrev(identifier)}"
85 cmd << " " + shell_quote("path:#{path}") unless path.empty? 85 cmd << " " + shell_quote("path:#{path}") unless path.empty?
86 shellout(cmd) do |io| 86 shellout(cmd) do |io|
87 io.each_line do |line| 87 io.each_line do |line|
88 # HG uses antislashs as separator on Windows 88 # HG uses antislashs as separator on Windows
89 line = line.gsub(/\\/, "/") 89 line = line.gsub(/\\/, "/")
99 end 99 end
100 end 100 end
101 return nil if $? && $?.exitstatus != 0 101 return nil if $? && $?.exitstatus != 0
102 entries.sort_by_name 102 entries.sort_by_name
103 end 103 end
104 104
105 # Fetch the revisions by using a template file that 105 # Fetch the revisions by using a template file that
106 # makes Mercurial produce a xml output. 106 # makes Mercurial produce a xml output.
107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) 107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
108 revisions = Revisions.new 108 revisions = Revisions.new
109 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}"
110 if identifier_from && identifier_to 110 if identifier_from && identifier_to
111 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}" 111 cmd << " -r #{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
112 elsif identifier_from 112 elsif identifier_from
113 cmd << " -r #{identifier_from.to_i}:" 113 cmd << " -r #{hgrev(identifier_from)}:"
114 end 114 end
115 cmd << " --limit #{options[:limit].to_i}" if options[:limit] 115 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
116 cmd << " #{shell_quote path}" unless path.blank? 116 cmd << " #{shell_quote path}" unless path.blank?
117 shellout(cmd) do |io| 117 shellout(cmd) do |io|
118 begin 118 begin
132 :from_path => from_path ? "/#{CGI.unescape(from_path)}" : nil, 132 :from_path => from_path ? "/#{CGI.unescape(from_path)}" : nil,
133 :from_revision => from_rev ? from_rev : nil 133 :from_revision => from_rev ? from_rev : nil
134 } 134 }
135 end 135 end
136 paths.sort! { |x,y| x[:path] <=> y[:path] } 136 paths.sort! { |x,y| x[:path] <=> y[:path] }
137 137
138 revisions << Revision.new({:identifier => logentry.attributes['revision'], 138 revisions << Revision.new({:identifier => logentry.attributes['revision'],
139 :scmid => logentry.attributes['node'], 139 :scmid => logentry.attributes['node'],
140 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), 140 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
141 :time => Time.parse(logentry.elements['date'].text).localtime, 141 :time => Time.parse(logentry.elements['date'].text).localtime,
142 :message => logentry.elements['msg'].text, 142 :message => logentry.elements['msg'].text,
148 end 148 end
149 end 149 end
150 return nil if $? && $?.exitstatus != 0 150 return nil if $? && $?.exitstatus != 0
151 revisions 151 revisions
152 end 152 end
153 153
154 def diff(path, identifier_from, identifier_to=nil) 154 def diff(path, identifier_from, identifier_to=nil)
155 path ||= '' 155 path ||= ''
156 diff_args = ''
157 diff = []
156 if identifier_to 158 if identifier_to
157 identifier_to = identifier_to.to_i 159 diff_args = "-r #{hgrev(identifier_to)} -r #{hgrev(identifier_from)}"
158 else 160 else
159 identifier_to = identifier_from.to_i - 1 161 if self.class.client_version_above?([1, 2])
160 end 162 diff_args = "-c #{hgrev(identifier_from)}"
161 if identifier_from 163 else
162 identifier_from = identifier_from.to_i 164 return []
163 end 165 end
164 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates" 166 end
167 cmd = "#{HG_BIN} -R #{target('')} --config diff.git=false diff --nodates #{diff_args}"
165 cmd << " -I #{target(path)}" unless path.empty? 168 cmd << " -I #{target(path)}" unless path.empty?
166 diff = []
167 shellout(cmd) do |io| 169 shellout(cmd) do |io|
168 io.each_line do |line| 170 io.each_line do |line|
169 diff << line 171 diff << line
170 end 172 end
171 end 173 end
172 return nil if $? && $?.exitstatus != 0 174 return nil if $? && $?.exitstatus != 0
173 diff 175 diff
174 end 176 end
175 177
176 def cat(path, identifier=nil) 178 def cat(path, identifier=nil)
177 cmd = "#{HG_BIN} -R #{target('')} cat" 179 cmd = "#{HG_BIN} -R #{target('')} cat"
178 cmd << " -r " + shell_quote(identifier ? identifier.to_s : "tip") 180 cmd << " -r #{hgrev(identifier)}"
179 cmd << " #{target(path)}" 181 cmd << " #{target(path)}"
180 cat = nil 182 cat = nil
181 shellout(cmd) do |io| 183 shellout(cmd) do |io|
182 io.binmode 184 io.binmode
183 cat = io.read 185 cat = io.read
184 end 186 end
185 return nil if $? && $?.exitstatus != 0 187 return nil if $? && $?.exitstatus != 0
186 cat 188 cat
187 end 189 end
188 190
189 def annotate(path, identifier=nil) 191 def annotate(path, identifier=nil)
190 path ||= '' 192 path ||= ''
191 cmd = "#{HG_BIN} -R #{target('')}" 193 cmd = "#{HG_BIN} -R #{target('')}"
192 cmd << " annotate -n -u" 194 cmd << " annotate -ncu"
193 cmd << " -r " + shell_quote(identifier ? identifier.to_s : "tip") 195 cmd << " -r #{hgrev(identifier)}"
194 cmd << " -r #{identifier.to_i}" if identifier
195 cmd << " #{target(path)}" 196 cmd << " #{target(path)}"
196 blame = Annotate.new 197 blame = Annotate.new
197 shellout(cmd) do |io| 198 shellout(cmd) do |io|
198 io.each_line do |line| 199 io.each_line do |line|
199 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$} 200 next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$}
200 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)
201 end 204 end
202 end 205 end
203 return nil if $? && $?.exitstatus != 0 206 return nil if $? && $?.exitstatus != 0
204 blame 207 blame
205 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
206 end 222 end
207 end 223 end
208 end 224 end
209 end 225 end