##// END OF EJS Templates
Fixed: error when browsing an empty Mercurial repository (#1046)....
Jean-Philippe Lang -
r1328:7aaa538fd9cb
parent child
Show More
@@ -1,92 +1,94
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/mercurial_adapter'
18 require 'redmine/scm/adapters/mercurial_adapter'
19
19
20 class Repository::Mercurial < Repository
20 class Repository::Mercurial < Repository
21 attr_protected :root_url
21 attr_protected :root_url
22 validates_presence_of :url
22 validates_presence_of :url
23
23
24 def scm_adapter
24 def scm_adapter
25 Redmine::Scm::Adapters::MercurialAdapter
25 Redmine::Scm::Adapters::MercurialAdapter
26 end
26 end
27
27
28 def self.scm_name
28 def self.scm_name
29 'Mercurial'
29 'Mercurial'
30 end
30 end
31
31
32 def entries(path=nil, identifier=nil)
32 def entries(path=nil, identifier=nil)
33 entries=scm.entries(path, identifier)
33 entries=scm.entries(path, identifier)
34 if entries
34 if entries
35 entries.each do |entry|
35 entries.each do |entry|
36 next unless entry.is_file?
36 next unless entry.is_file?
37 # Set the filesize unless browsing a specific revision
37 # Set the filesize unless browsing a specific revision
38 if identifier.nil?
38 if identifier.nil?
39 full_path = File.join(root_url, entry.path)
39 full_path = File.join(root_url, entry.path)
40 entry.size = File.stat(full_path).size if File.file?(full_path)
40 entry.size = File.stat(full_path).size if File.file?(full_path)
41 end
41 end
42 # Search the DB for the entry's last change
42 # Search the DB for the entry's last change
43 change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
43 change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
44 if change
44 if change
45 entry.lastrev.identifier = change.changeset.revision
45 entry.lastrev.identifier = change.changeset.revision
46 entry.lastrev.name = change.changeset.revision
46 entry.lastrev.name = change.changeset.revision
47 entry.lastrev.author = change.changeset.committer
47 entry.lastrev.author = change.changeset.committer
48 entry.lastrev.revision = change.revision
48 entry.lastrev.revision = change.revision
49 end
49 end
50 end
50 end
51 end
51 end
52 entries
52 entries
53 end
53 end
54
54
55 def fetch_changesets
55 def fetch_changesets
56 scm_info = scm.info
56 scm_info = scm.info
57 if scm_info
57 if scm_info
58 # latest revision found in database
58 # latest revision found in database
59 db_revision = latest_changeset ? latest_changeset.revision.to_i : -1
59 db_revision = latest_changeset ? latest_changeset.revision.to_i : -1
60 # latest revision in the repository
60 # latest revision in the repository
61 scm_revision = scm_info.lastrev.identifier.to_i
61 latest_revision = scm_info.lastrev
62 return if latest_revision.nil?
63 scm_revision = latest_revision.identifier.to_i
62 if db_revision < scm_revision
64 if db_revision < scm_revision
63 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
65 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
64 identifier_from = db_revision + 1
66 identifier_from = db_revision + 1
65 while (identifier_from <= scm_revision)
67 while (identifier_from <= scm_revision)
66 # loads changesets by batches of 100
68 # loads changesets by batches of 100
67 identifier_to = [identifier_from + 99, scm_revision].min
69 identifier_to = [identifier_from + 99, scm_revision].min
68 revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true)
70 revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true)
69 transaction do
71 transaction do
70 revisions.each do |revision|
72 revisions.each do |revision|
71 changeset = Changeset.create(:repository => self,
73 changeset = Changeset.create(:repository => self,
72 :revision => revision.identifier,
74 :revision => revision.identifier,
73 :scmid => revision.scmid,
75 :scmid => revision.scmid,
74 :committer => revision.author,
76 :committer => revision.author,
75 :committed_on => revision.time,
77 :committed_on => revision.time,
76 :comments => revision.message)
78 :comments => revision.message)
77
79
78 revision.paths.each do |change|
80 revision.paths.each do |change|
79 Change.create(:changeset => changeset,
81 Change.create(:changeset => changeset,
80 :action => change[:action],
82 :action => change[:action],
81 :path => change[:path],
83 :path => change[:path],
82 :from_path => change[:from_path],
84 :from_path => change[:from_path],
83 :from_revision => change[:from_revision])
85 :from_revision => change[:from_revision])
84 end
86 end
85 end
87 end
86 end unless revisions.nil?
88 end unless revisions.nil?
87 identifier_from = identifier_to + 1
89 identifier_from = identifier_to + 1
88 end
90 end
89 end
91 end
90 end
92 end
91 end
93 end
92 end
94 end
@@ -1,206 +1,207
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 << build_revision_from_changeset(changeset)
91 revisions << build_revision_from_changeset(changeset)
92 changeset = {}
92 changeset = {}
93 end
93 end
94 if !parsing_descr
94 if !parsing_descr
95 changeset.store key.to_sym, value
95 changeset.store key.to_sym, value
96 if $1 == "description"
96 if $1 == "description"
97 parsing_descr = true
97 parsing_descr = true
98 line_feeds = 0
98 line_feeds = 0
99 next
99 next
100 end
100 end
101 end
101 end
102 end
102 end
103 if parsing_descr
103 if parsing_descr
104 changeset[:description] << line
104 changeset[:description] << line
105 line_feeds += 1 if line.chomp.empty?
105 line_feeds += 1 if line.chomp.empty?
106 end
106 end
107 end
107 end
108 revisions << build_revision_from_changeset(changeset)
108 # Add the last changeset if there is one left
109 revisions << build_revision_from_changeset(changeset) if changeset[:date]
109 end
110 end
110 return nil if $? && $?.exitstatus != 0
111 return nil if $? && $?.exitstatus != 0
111 revisions
112 revisions
112 end
113 end
113
114
114 def diff(path, identifier_from, identifier_to=nil, type="inline")
115 def diff(path, identifier_from, identifier_to=nil, type="inline")
115 path ||= ''
116 path ||= ''
116 if identifier_to
117 if identifier_to
117 identifier_to = identifier_to.to_i
118 identifier_to = identifier_to.to_i
118 else
119 else
119 identifier_to = identifier_from.to_i - 1
120 identifier_to = identifier_from.to_i - 1
120 end
121 end
121 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
122 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
122 cmd << " -I #{target(path)}" unless path.empty?
123 cmd << " -I #{target(path)}" unless path.empty?
123 diff = []
124 diff = []
124 shellout(cmd) do |io|
125 shellout(cmd) do |io|
125 io.each_line do |line|
126 io.each_line do |line|
126 diff << line
127 diff << line
127 end
128 end
128 end
129 end
129 return nil if $? && $?.exitstatus != 0
130 return nil if $? && $?.exitstatus != 0
130 DiffTableList.new diff, type
131 DiffTableList.new diff, type
131 end
132 end
132
133
133 def cat(path, identifier=nil)
134 def cat(path, identifier=nil)
134 cmd = "#{HG_BIN} -R #{target('')} cat"
135 cmd = "#{HG_BIN} -R #{target('')} cat"
135 cmd << " -r #{identifier.to_i}" if identifier
136 cmd << " -r #{identifier.to_i}" if identifier
136 cmd << " #{target(path)}"
137 cmd << " #{target(path)}"
137 cat = nil
138 cat = nil
138 shellout(cmd) do |io|
139 shellout(cmd) do |io|
139 io.binmode
140 io.binmode
140 cat = io.read
141 cat = io.read
141 end
142 end
142 return nil if $? && $?.exitstatus != 0
143 return nil if $? && $?.exitstatus != 0
143 cat
144 cat
144 end
145 end
145
146
146 def annotate(path, identifier=nil)
147 def annotate(path, identifier=nil)
147 path ||= ''
148 path ||= ''
148 cmd = "#{HG_BIN} -R #{target('')}"
149 cmd = "#{HG_BIN} -R #{target('')}"
149 cmd << " annotate -n -u"
150 cmd << " annotate -n -u"
150 cmd << " -r #{identifier.to_i}" if identifier
151 cmd << " -r #{identifier.to_i}" if identifier
151 cmd << " #{target(path)}"
152 cmd << " #{target(path)}"
152 blame = Annotate.new
153 blame = Annotate.new
153 shellout(cmd) do |io|
154 shellout(cmd) do |io|
154 io.each_line do |line|
155 io.each_line do |line|
155 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
156 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
156 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
157 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
157 end
158 end
158 end
159 end
159 return nil if $? && $?.exitstatus != 0
160 return nil if $? && $?.exitstatus != 0
160 blame
161 blame
161 end
162 end
162
163
163 private
164 private
164
165
165 # Builds a revision objet from the changeset returned by hg command
166 # Builds a revision objet from the changeset returned by hg command
166 def build_revision_from_changeset(changeset)
167 def build_revision_from_changeset(changeset)
167 rev_id = changeset[:changeset].to_s.split(':').first.to_i
168 rev_id = changeset[:changeset].to_s.split(':').first.to_i
168
169
169 # Changes
170 # Changes
170 paths = (rev_id == 0) ?
171 paths = (rev_id == 0) ?
171 # Can't get changes for revision 0 with hg status
172 # Can't get changes for revision 0 with hg status
172 changeset[:files].to_s.split.collect{|path| {:action => 'A', :path => "/#{path}"}} :
173 changeset[:files].to_s.split.collect{|path| {:action => 'A', :path => "/#{path}"}} :
173 status(rev_id)
174 status(rev_id)
174
175
175 Revision.new({:identifier => rev_id,
176 Revision.new({:identifier => rev_id,
176 :scmid => changeset[:changeset].to_s.split(':').last,
177 :scmid => changeset[:changeset].to_s.split(':').last,
177 :author => changeset[:user],
178 :author => changeset[:user],
178 :time => Time.parse(changeset[:date]),
179 :time => Time.parse(changeset[:date]),
179 :message => changeset[:description],
180 :message => changeset[:description],
180 :paths => paths
181 :paths => paths
181 })
182 })
182 end
183 end
183
184
184 # Returns the file changes for a given revision
185 # Returns the file changes for a given revision
185 def status(rev_id)
186 def status(rev_id)
186 cmd = "#{HG_BIN} -R #{target('')} status --rev #{rev_id.to_i - 1}:#{rev_id.to_i}"
187 cmd = "#{HG_BIN} -R #{target('')} status --rev #{rev_id.to_i - 1}:#{rev_id.to_i}"
187 result = []
188 result = []
188 shellout(cmd) do |io|
189 shellout(cmd) do |io|
189 io.each_line do |line|
190 io.each_line do |line|
190 action, file = line.chomp.split
191 action, file = line.chomp.split
191 next unless action && file
192 next unless action && file
192 file.gsub!("\\", "/")
193 file.gsub!("\\", "/")
193 case action
194 case action
194 when 'R'
195 when 'R'
195 result << { :action => 'D' , :path => "/#{file}" }
196 result << { :action => 'D' , :path => "/#{file}" }
196 else
197 else
197 result << { :action => action, :path => "/#{file}" }
198 result << { :action => action, :path => "/#{file}" }
198 end
199 end
199 end
200 end
200 end
201 end
201 result
202 result
202 end
203 end
203 end
204 end
204 end
205 end
205 end
206 end
206 end
207 end
General Comments 0
You need to be logged in to leave comments. Login now