##// END OF EJS Templates
Mercurial: Get proper file action on revision (#983)....
Jean-Philippe Lang -
r1317:db7f890030b8
parent child
Show More
@@ -1,177 +1,206
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
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
19
20 module Redmine
20 module Redmine
21 module Scm
21 module Scm
22 module Adapters
22 module Adapters
23 class MercurialAdapter < AbstractAdapter
23 class MercurialAdapter < AbstractAdapter
24
24
25 # Mercurial executable name
25 # Mercurial executable name
26 HG_BIN = "hg"
26 HG_BIN = "hg"
27
27
28 def info
28 def info
29 cmd = "#{HG_BIN} -R #{target('')} root"
29 cmd = "#{HG_BIN} -R #{target('')} root"
30 root_url = nil
30 root_url = nil
31 shellout(cmd) do |io|
31 shellout(cmd) do |io|
32 root_url = io.gets
32 root_url = io.gets
33 end
33 end
34 return nil if $? && $?.exitstatus != 0
34 return nil if $? && $?.exitstatus != 0
35 info = Info.new({:root_url => root_url.chomp,
35 info = Info.new({:root_url => root_url.chomp,
36 :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
36 :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
37 })
37 })
38 info
38 info
39 rescue CommandFailed
39 rescue CommandFailed
40 return nil
40 return nil
41 end
41 end
42
42
43 def entries(path=nil, identifier=nil)
43 def entries(path=nil, identifier=nil)
44 path ||= ''
44 path ||= ''
45 entries = Entries.new
45 entries = Entries.new
46 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target(path)} locate"
46 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target(path)} locate"
47 cmd << " -r #{identifier.to_i}" if identifier
47 cmd << " -r #{identifier.to_i}" if identifier
48 cmd << " " + shell_quote('glob:**')
48 cmd << " " + shell_quote('glob:**')
49 shellout(cmd) do |io|
49 shellout(cmd) do |io|
50 io.each_line do |line|
50 io.each_line do |line|
51 e = line.chomp.split(%r{[\/\\]})
51 e = line.chomp.split(%r{[\/\\]})
52 entries << Entry.new({:name => e.first,
52 entries << Entry.new({:name => e.first,
53 :path => (path.empty? ? e.first : "#{path}/#{e.first}"),
53 :path => (path.empty? ? e.first : "#{path}/#{e.first}"),
54 :kind => (e.size > 1 ? 'dir' : 'file'),
54 :kind => (e.size > 1 ? 'dir' : 'file'),
55 :lastrev => Revision.new
55 :lastrev => Revision.new
56 }) unless entries.detect{|entry| entry.name == e.first}
56 }) unless entries.detect{|entry| entry.name == e.first}
57 end
57 end
58 end
58 end
59 return nil if $? && $?.exitstatus != 0
59 return nil if $? && $?.exitstatus != 0
60 entries.sort_by_name
60 entries.sort_by_name
61 end
61 end
62
62
63 def entry(path=nil, identifier=nil)
63 def entry(path=nil, identifier=nil)
64 path ||= ''
64 path ||= ''
65 search_path = path.split('/')[0..-2].join('/')
65 search_path = path.split('/')[0..-2].join('/')
66 entry_name = path.split('/').last
66 entry_name = path.split('/').last
67 e = entries(search_path, identifier)
67 e = entries(search_path, identifier)
68 e ? e.detect{|entry| entry.name == entry_name} : nil
68 e ? e.detect{|entry| entry.name == entry_name} : nil
69 end
69 end
70
70
71 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
71 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
72 revisions = Revisions.new
72 revisions = Revisions.new
73 cmd = "#{HG_BIN} -v --encoding utf8 -R #{target('')} log"
73 cmd = "#{HG_BIN} -v --encoding utf8 -R #{target('')} log"
74 if identifier_from && identifier_to
74 if identifier_from && identifier_to
75 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
75 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
76 elsif identifier_from
76 elsif identifier_from
77 cmd << " -r #{identifier_from.to_i}:"
77 cmd << " -r #{identifier_from.to_i}:"
78 end
78 end
79 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
79 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
80 shellout(cmd) do |io|
80 shellout(cmd) do |io|
81 changeset = {}
81 changeset = {}
82 parsing_descr = false
82 parsing_descr = false
83 line_feeds = 0
83 line_feeds = 0
84
84
85 io.each_line do |line|
85 io.each_line do |line|
86 if line =~ /^(\w+):\s*(.*)$/
86 if line =~ /^(\w+):\s*(.*)$/
87 key = $1
87 key = $1
88 value = $2
88 value = $2
89 if parsing_descr && line_feeds > 1
89 if parsing_descr && line_feeds > 1
90 parsing_descr = false
90 parsing_descr = false
91 revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i,
91 revisions << build_revision_from_changeset(changeset)
92 :scmid => changeset[:changeset].split(':').last,
93 :author => changeset[:user],
94 :time => Time.parse(changeset[:date]),
95 :message => changeset[:description],
96 :paths => changeset[:files].to_s.split.collect{|path| {:action => 'X', :path => "/#{path}"}}
97 })
98 changeset = {}
92 changeset = {}
99 end
93 end
100 if !parsing_descr
94 if !parsing_descr
101 changeset.store key.to_sym, value
95 changeset.store key.to_sym, value
102 if $1 == "description"
96 if $1 == "description"
103 parsing_descr = true
97 parsing_descr = true
104 line_feeds = 0
98 line_feeds = 0
105 next
99 next
106 end
100 end
107 end
101 end
108 end
102 end
109 if parsing_descr
103 if parsing_descr
110 changeset[:description] << line
104 changeset[:description] << line
111 line_feeds += 1 if line.chomp.empty?
105 line_feeds += 1 if line.chomp.empty?
112 end
106 end
113 end
107 end
114 revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i,
108 revisions << build_revision_from_changeset(changeset)
115 :scmid => changeset[:changeset].split(':').last,
116 :author => changeset[:user],
117 :time => Time.parse(changeset[:date]),
118 :message => changeset[:description],
119 :paths => changeset[:files].to_s.split.collect{|path| {:action => 'X', :path => "/#{path}"}}
120 })
121 end
109 end
122 return nil if $? && $?.exitstatus != 0
110 return nil if $? && $?.exitstatus != 0
123 revisions
111 revisions
124 end
112 end
125
113
126 def diff(path, identifier_from, identifier_to=nil, type="inline")
114 def diff(path, identifier_from, identifier_to=nil, type="inline")
127 path ||= ''
115 path ||= ''
128 if identifier_to
116 if identifier_to
129 identifier_to = identifier_to.to_i
117 identifier_to = identifier_to.to_i
130 else
118 else
131 identifier_to = identifier_from.to_i - 1
119 identifier_to = identifier_from.to_i - 1
132 end
120 end
133 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
121 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
134 cmd << " -I #{target(path)}" unless path.empty?
122 cmd << " -I #{target(path)}" unless path.empty?
135 diff = []
123 diff = []
136 shellout(cmd) do |io|
124 shellout(cmd) do |io|
137 io.each_line do |line|
125 io.each_line do |line|
138 diff << line
126 diff << line
139 end
127 end
140 end
128 end
141 return nil if $? && $?.exitstatus != 0
129 return nil if $? && $?.exitstatus != 0
142 DiffTableList.new diff, type
130 DiffTableList.new diff, type
143 end
131 end
144
132
145 def cat(path, identifier=nil)
133 def cat(path, identifier=nil)
146 cmd = "#{HG_BIN} -R #{target('')} cat"
134 cmd = "#{HG_BIN} -R #{target('')} cat"
147 cmd << " -r #{identifier.to_i}" if identifier
135 cmd << " -r #{identifier.to_i}" if identifier
148 cmd << " #{target(path)}"
136 cmd << " #{target(path)}"
149 cat = nil
137 cat = nil
150 shellout(cmd) do |io|
138 shellout(cmd) do |io|
151 io.binmode
139 io.binmode
152 cat = io.read
140 cat = io.read
153 end
141 end
154 return nil if $? && $?.exitstatus != 0
142 return nil if $? && $?.exitstatus != 0
155 cat
143 cat
156 end
144 end
157
145
158 def annotate(path, identifier=nil)
146 def annotate(path, identifier=nil)
159 path ||= ''
147 path ||= ''
160 cmd = "#{HG_BIN} -R #{target('')}"
148 cmd = "#{HG_BIN} -R #{target('')}"
161 cmd << " annotate -n -u"
149 cmd << " annotate -n -u"
162 cmd << " -r #{identifier.to_i}" if identifier
150 cmd << " -r #{identifier.to_i}" if identifier
163 cmd << " #{target(path)}"
151 cmd << " #{target(path)}"
164 blame = Annotate.new
152 blame = Annotate.new
165 shellout(cmd) do |io|
153 shellout(cmd) do |io|
166 io.each_line do |line|
154 io.each_line do |line|
167 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
155 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
168 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
156 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
169 end
157 end
170 end
158 end
171 return nil if $? && $?.exitstatus != 0
159 return nil if $? && $?.exitstatus != 0
172 blame
160 blame
173 end
161 end
162
163 private
164
165 # Builds a revision objet from the changeset returned by hg command
166 def build_revision_from_changeset(changeset)
167 rev_id = changeset[:changeset].to_s.split(':').first.to_i
168
169 # Changes
170 paths = (rev_id == 0) ?
171 # Can't get changes for revision 0 with hg status
172 changeset[:files].to_s.split.collect{|path| {:action => 'A', :path => "/#{path}"}} :
173 status(rev_id)
174
175 Revision.new({:identifier => rev_id,
176 :scmid => changeset[:changeset].to_s.split(':').last,
177 :author => changeset[:user],
178 :time => Time.parse(changeset[:date]),
179 :message => changeset[:description],
180 :paths => paths
181 })
182 end
183
184 # Returns the file changes for a given revision
185 def status(rev_id)
186 cmd = "#{HG_BIN} -R #{target('')} status --rev #{rev_id.to_i - 1}:#{rev_id.to_i}"
187 result = []
188 shellout(cmd) do |io|
189 io.each_line do |line|
190 action, file = line.chomp.split
191 next unless action && file
192 file.gsub!("\\", "/")
193 case action
194 when 'R'
195 result << { :action => 'D' , :path => "/#{file}" }
196 else
197 result << { :action => action, :path => "/#{file}" }
198 end
199 end
200 end
201 result
202 end
174 end
203 end
175 end
204 end
176 end
205 end
177 end
206 end
General Comments 0
You need to be logged in to leave comments. Login now