##// END OF EJS Templates
Mercurial adapter:...
Jean-Philippe Lang -
r1240:01fdaf597742
parent child
Show More
@@ -1,81 +1,87
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 # Search the DB for the entry's last change
37 # Search the DB for the entry's last change
38 change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
38 change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
39 if change
39 if change
40 entry.lastrev.identifier = change.changeset.revision
40 entry.lastrev.identifier = change.changeset.revision
41 entry.lastrev.name = change.changeset.revision
41 entry.lastrev.name = change.changeset.revision
42 entry.lastrev.author = change.changeset.committer
42 entry.lastrev.author = change.changeset.committer
43 entry.lastrev.revision = change.revision
43 entry.lastrev.revision = change.revision
44 end
44 end
45 end
45 end
46 end
46 end
47 entries
47 entries
48 end
48 end
49
49
50 def fetch_changesets
50 def fetch_changesets
51 scm_info = scm.info
51 scm_info = scm.info
52 if scm_info
52 if scm_info
53 # latest revision found in database
53 # latest revision found in database
54 db_revision = latest_changeset ? latest_changeset.revision : nil
54 db_revision = latest_changeset ? latest_changeset.revision.to_i : -1
55 # latest revision in the repository
55 # latest revision in the repository
56 scm_revision = scm_info.lastrev.identifier.to_i
56 scm_revision = scm_info.lastrev.identifier.to_i
57
57 if db_revision < scm_revision
58 unless changesets.find_by_revision(scm_revision)
58 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
59 revisions = scm.revisions('', db_revision, nil)
59 identifier_from = db_revision + 1
60 transaction do
60 while (identifier_from <= scm_revision)
61 revisions.reverse_each do |revision|
61 # loads changesets by batches of 100
62 changeset = Changeset.create(:repository => self,
62 identifier_to = [identifier_from + 99, scm_revision].min
63 :revision => revision.identifier,
63 revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true)
64 :scmid => revision.scmid,
64 transaction do
65 :committer => revision.author,
65 revisions.each do |revision|
66 :committed_on => revision.time,
66 changeset = Changeset.create(:repository => self,
67 :comments => revision.message)
67 :revision => revision.identifier,
68
68 :scmid => revision.scmid,
69 revision.paths.each do |change|
69 :committer => revision.author,
70 Change.create(:changeset => changeset,
70 :committed_on => revision.time,
71 :action => change[:action],
71 :comments => revision.message)
72 :path => change[:path],
72
73 :from_path => change[:from_path],
73 revision.paths.each do |change|
74 :from_revision => change[:from_revision])
74 Change.create(:changeset => changeset,
75 :action => change[:action],
76 :path => change[:path],
77 :from_path => change[:from_path],
78 :from_revision => change[:from_revision])
79 end
75 end
80 end
76 end
81 end unless revisions.nil?
82 identifier_from = identifier_to + 1
77 end
83 end
78 end
84 end
79 end
85 end
80 end
86 end
81 end
87 end
@@ -1,171 +1,175
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 -R #{target('')} log"
73 cmd = "#{HG_BIN} -v -R #{target('')} log"
74 cmd << " -r #{identifier_from.to_i}:" if identifier_from
74 if identifier_from && identifier_to
75 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
76 elsif identifier_from
77 cmd << " -r #{identifier_from.to_i}:"
78 end
75 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
79 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
76 shellout(cmd) do |io|
80 shellout(cmd) do |io|
77 changeset = {}
81 changeset = {}
78 parsing_descr = false
82 parsing_descr = false
79 line_feeds = 0
83 line_feeds = 0
80
84
81 io.each_line do |line|
85 io.each_line do |line|
82 if line =~ /^(\w+):\s*(.*)$/
86 if line =~ /^(\w+):\s*(.*)$/
83 key = $1
87 key = $1
84 value = $2
88 value = $2
85 if parsing_descr && line_feeds > 1
89 if parsing_descr && line_feeds > 1
86 parsing_descr = false
90 parsing_descr = false
87 revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i,
91 revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i,
88 :scmid => changeset[:changeset].split(':').last,
92 :scmid => changeset[:changeset].split(':').last,
89 :author => changeset[:user],
93 :author => changeset[:user],
90 :time => Time.parse(changeset[:date]),
94 :time => Time.parse(changeset[:date]),
91 :message => changeset[:description],
95 :message => changeset[:description],
92 :paths => changeset[:files].to_s.split.collect{|path| {:action => 'X', :path => "/#{path}"}}
96 :paths => changeset[:files].to_s.split.collect{|path| {:action => 'X', :path => "/#{path}"}}
93 })
97 })
94 changeset = {}
98 changeset = {}
95 end
99 end
96 if !parsing_descr
100 if !parsing_descr
97 changeset.store key.to_sym, value
101 changeset.store key.to_sym, value
98 if $1 == "description"
102 if $1 == "description"
99 parsing_descr = true
103 parsing_descr = true
100 line_feeds = 0
104 line_feeds = 0
101 next
105 next
102 end
106 end
103 end
107 end
104 end
108 end
105 if parsing_descr
109 if parsing_descr
106 changeset[:description] << line
110 changeset[:description] << line
107 line_feeds += 1 if line.chomp.empty?
111 line_feeds += 1 if line.chomp.empty?
108 end
112 end
109 end
113 end
110 revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i,
114 revisions << Revision.new({:identifier => changeset[:changeset].split(':').first.to_i,
111 :scmid => changeset[:changeset].split(':').last,
115 :scmid => changeset[:changeset].split(':').last,
112 :author => changeset[:user],
116 :author => changeset[:user],
113 :time => Time.parse(changeset[:date]),
117 :time => Time.parse(changeset[:date]),
114 :message => changeset[:description],
118 :message => changeset[:description],
115 :paths => changeset[:files].to_s.split.collect{|path| {:action => 'X', :path => "/#{path}"}}
119 :paths => changeset[:files].to_s.split.collect{|path| {:action => 'X', :path => "/#{path}"}}
116 })
120 })
117 end
121 end
118 return nil if $? && $?.exitstatus != 0
122 return nil if $? && $?.exitstatus != 0
119 revisions
123 revisions
120 end
124 end
121
125
122 def diff(path, identifier_from, identifier_to=nil, type="inline")
126 def diff(path, identifier_from, identifier_to=nil, type="inline")
123 path ||= ''
127 path ||= ''
124 if identifier_to
128 if identifier_to
125 identifier_to = identifier_to.to_i
129 identifier_to = identifier_to.to_i
126 else
130 else
127 identifier_to = identifier_from.to_i - 1
131 identifier_to = identifier_from.to_i - 1
128 end
132 end
129 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
133 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
130 cmd << " -I #{target(path)}" unless path.empty?
134 cmd << " -I #{target(path)}" unless path.empty?
131 diff = []
135 diff = []
132 shellout(cmd) do |io|
136 shellout(cmd) do |io|
133 io.each_line do |line|
137 io.each_line do |line|
134 diff << line
138 diff << line
135 end
139 end
136 end
140 end
137 return nil if $? && $?.exitstatus != 0
141 return nil if $? && $?.exitstatus != 0
138 DiffTableList.new diff, type
142 DiffTableList.new diff, type
139 end
143 end
140
144
141 def cat(path, identifier=nil)
145 def cat(path, identifier=nil)
142 cmd = "#{HG_BIN} -R #{target('')} cat #{target(path)}"
146 cmd = "#{HG_BIN} -R #{target('')} cat #{target(path)}"
143 cat = nil
147 cat = nil
144 shellout(cmd) do |io|
148 shellout(cmd) do |io|
145 io.binmode
149 io.binmode
146 cat = io.read
150 cat = io.read
147 end
151 end
148 return nil if $? && $?.exitstatus != 0
152 return nil if $? && $?.exitstatus != 0
149 cat
153 cat
150 end
154 end
151
155
152 def annotate(path, identifier=nil)
156 def annotate(path, identifier=nil)
153 path ||= ''
157 path ||= ''
154 cmd = "#{HG_BIN} -R #{target('')}"
158 cmd = "#{HG_BIN} -R #{target('')}"
155 cmd << " annotate -n -u"
159 cmd << " annotate -n -u"
156 cmd << " -r #{identifier.to_i}" if identifier
160 cmd << " -r #{identifier.to_i}" if identifier
157 cmd << " #{target(path)}"
161 cmd << " #{target(path)}"
158 blame = Annotate.new
162 blame = Annotate.new
159 shellout(cmd) do |io|
163 shellout(cmd) do |io|
160 io.each_line do |line|
164 io.each_line do |line|
161 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
165 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
162 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
166 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
163 end
167 end
164 end
168 end
165 return nil if $? && $?.exitstatus != 0
169 return nil if $? && $?.exitstatus != 0
166 blame
170 blame
167 end
171 end
168 end
172 end
169 end
173 end
170 end
174 end
171 end
175 end
General Comments 0
You need to be logged in to leave comments. Login now