##// END OF EJS Templates
Fixed that some arguments where not properly escaped in scm adapters....
Jean-Philippe Lang -
r4425:7d7c67dabad1
parent child
Show More
@@ -1,186 +1,189
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 BazaarAdapter < AbstractAdapter
23 class BazaarAdapter < AbstractAdapter
24
24
25 # Bazaar executable name
25 # Bazaar executable name
26 BZR_BIN = "bzr"
26 BZR_BIN = "bzr"
27
27
28 # Get info about the repository
28 # Get info about the repository
29 def info
29 def info
30 cmd = "#{BZR_BIN} revno #{target('')}"
30 cmd = "#{BZR_BIN} revno #{target('')}"
31 info = nil
31 info = nil
32 shellout(cmd) do |io|
32 shellout(cmd) do |io|
33 if io.read =~ %r{^(\d+)\r?$}
33 if io.read =~ %r{^(\d+)\r?$}
34 info = Info.new({:root_url => url,
34 info = Info.new({:root_url => url,
35 :lastrev => Revision.new({
35 :lastrev => Revision.new({
36 :identifier => $1
36 :identifier => $1
37 })
37 })
38 })
38 })
39 end
39 end
40 end
40 end
41 return nil if $? && $?.exitstatus != 0
41 return nil if $? && $?.exitstatus != 0
42 info
42 info
43 rescue CommandFailed
43 rescue CommandFailed
44 return nil
44 return nil
45 end
45 end
46
46
47 # Returns an Entries collection
47 # Returns an Entries collection
48 # or nil if the given path doesn't exist in the repository
48 # or nil if the given path doesn't exist in the repository
49 def entries(path=nil, identifier=nil)
49 def entries(path=nil, identifier=nil)
50 path ||= ''
50 path ||= ''
51 entries = Entries.new
51 entries = Entries.new
52 cmd = "#{BZR_BIN} ls -v --show-ids"
52 cmd = "#{BZR_BIN} ls -v --show-ids"
53 identifier = -1 unless identifier && identifier.to_i > 0
53 identifier = -1 unless identifier && identifier.to_i > 0
54 cmd << " -r#{identifier.to_i}"
54 cmd << " -r#{identifier.to_i}"
55 cmd << " #{target(path)}"
55 cmd << " #{target(path)}"
56 shellout(cmd) do |io|
56 shellout(cmd) do |io|
57 prefix = "#{url}/#{path}".gsub('\\', '/')
57 prefix = "#{url}/#{path}".gsub('\\', '/')
58 logger.debug "PREFIX: #{prefix}"
58 logger.debug "PREFIX: #{prefix}"
59 re = %r{^V\s+(#{Regexp.escape(prefix)})?(\/?)([^\/]+)(\/?)\s+(\S+)\r?$}
59 re = %r{^V\s+(#{Regexp.escape(prefix)})?(\/?)([^\/]+)(\/?)\s+(\S+)\r?$}
60 io.each_line do |line|
60 io.each_line do |line|
61 next unless line =~ re
61 next unless line =~ re
62 entries << Entry.new({:name => $3.strip,
62 entries << Entry.new({:name => $3.strip,
63 :path => ((path.empty? ? "" : "#{path}/") + $3.strip),
63 :path => ((path.empty? ? "" : "#{path}/") + $3.strip),
64 :kind => ($4.blank? ? 'file' : 'dir'),
64 :kind => ($4.blank? ? 'file' : 'dir'),
65 :size => nil,
65 :size => nil,
66 :lastrev => Revision.new(:revision => $5.strip)
66 :lastrev => Revision.new(:revision => $5.strip)
67 })
67 })
68 end
68 end
69 end
69 end
70 return nil if $? && $?.exitstatus != 0
70 return nil if $? && $?.exitstatus != 0
71 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
71 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
72 entries.sort_by_name
72 entries.sort_by_name
73 end
73 end
74
74
75 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
75 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
76 path ||= ''
76 path ||= ''
77 identifier_from = 'last:1' unless identifier_from and identifier_from.to_i > 0
77 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : 'last:1'
78 identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
78 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
79 revisions = Revisions.new
79 revisions = Revisions.new
80 cmd = "#{BZR_BIN} log -v --show-ids -r#{identifier_to.to_i}..#{identifier_from} #{target(path)}"
80 cmd = "#{BZR_BIN} log -v --show-ids -r#{identifier_to}..#{identifier_from} #{target(path)}"
81 shellout(cmd) do |io|
81 shellout(cmd) do |io|
82 revision = nil
82 revision = nil
83 parsing = nil
83 parsing = nil
84 io.each_line do |line|
84 io.each_line do |line|
85 if line =~ /^----/
85 if line =~ /^----/
86 revisions << revision if revision
86 revisions << revision if revision
87 revision = Revision.new(:paths => [], :message => '')
87 revision = Revision.new(:paths => [], :message => '')
88 parsing = nil
88 parsing = nil
89 else
89 else
90 next unless revision
90 next unless revision
91
91
92 if line =~ /^revno: (\d+)($|\s\[merge\]$)/
92 if line =~ /^revno: (\d+)($|\s\[merge\]$)/
93 revision.identifier = $1.to_i
93 revision.identifier = $1.to_i
94 elsif line =~ /^committer: (.+)$/
94 elsif line =~ /^committer: (.+)$/
95 revision.author = $1.strip
95 revision.author = $1.strip
96 elsif line =~ /^revision-id:(.+)$/
96 elsif line =~ /^revision-id:(.+)$/
97 revision.scmid = $1.strip
97 revision.scmid = $1.strip
98 elsif line =~ /^timestamp: (.+)$/
98 elsif line =~ /^timestamp: (.+)$/
99 revision.time = Time.parse($1).localtime
99 revision.time = Time.parse($1).localtime
100 elsif line =~ /^ -----/
100 elsif line =~ /^ -----/
101 # partial revisions
101 # partial revisions
102 parsing = nil unless parsing == 'message'
102 parsing = nil unless parsing == 'message'
103 elsif line =~ /^(message|added|modified|removed|renamed):/
103 elsif line =~ /^(message|added|modified|removed|renamed):/
104 parsing = $1
104 parsing = $1
105 elsif line =~ /^ (.*)$/
105 elsif line =~ /^ (.*)$/
106 if parsing == 'message'
106 if parsing == 'message'
107 revision.message << "#{$1}\n"
107 revision.message << "#{$1}\n"
108 else
108 else
109 if $1 =~ /^(.*)\s+(\S+)$/
109 if $1 =~ /^(.*)\s+(\S+)$/
110 path = $1.strip
110 path = $1.strip
111 revid = $2
111 revid = $2
112 case parsing
112 case parsing
113 when 'added'
113 when 'added'
114 revision.paths << {:action => 'A', :path => "/#{path}", :revision => revid}
114 revision.paths << {:action => 'A', :path => "/#{path}", :revision => revid}
115 when 'modified'
115 when 'modified'
116 revision.paths << {:action => 'M', :path => "/#{path}", :revision => revid}
116 revision.paths << {:action => 'M', :path => "/#{path}", :revision => revid}
117 when 'removed'
117 when 'removed'
118 revision.paths << {:action => 'D', :path => "/#{path}", :revision => revid}
118 revision.paths << {:action => 'D', :path => "/#{path}", :revision => revid}
119 when 'renamed'
119 when 'renamed'
120 new_path = path.split('=>').last
120 new_path = path.split('=>').last
121 revision.paths << {:action => 'M', :path => "/#{new_path.strip}", :revision => revid} if new_path
121 revision.paths << {:action => 'M', :path => "/#{new_path.strip}", :revision => revid} if new_path
122 end
122 end
123 end
123 end
124 end
124 end
125 else
125 else
126 parsing = nil
126 parsing = nil
127 end
127 end
128 end
128 end
129 end
129 end
130 revisions << revision if revision
130 revisions << revision if revision
131 end
131 end
132 return nil if $? && $?.exitstatus != 0
132 return nil if $? && $?.exitstatus != 0
133 revisions
133 revisions
134 end
134 end
135
135
136 def diff(path, identifier_from, identifier_to=nil)
136 def diff(path, identifier_from, identifier_to=nil)
137 path ||= ''
137 path ||= ''
138 if identifier_to
138 if identifier_to
139 identifier_to = identifier_to.to_i
139 identifier_to = identifier_to.to_i
140 else
140 else
141 identifier_to = identifier_from.to_i - 1
141 identifier_to = identifier_from.to_i - 1
142 end
142 end
143 if identifier_from
144 identifier_from = identifier_from.to_i
145 end
143 cmd = "#{BZR_BIN} diff -r#{identifier_to}..#{identifier_from} #{target(path)}"
146 cmd = "#{BZR_BIN} diff -r#{identifier_to}..#{identifier_from} #{target(path)}"
144 diff = []
147 diff = []
145 shellout(cmd) do |io|
148 shellout(cmd) do |io|
146 io.each_line do |line|
149 io.each_line do |line|
147 diff << line
150 diff << line
148 end
151 end
149 end
152 end
150 #return nil if $? && $?.exitstatus != 0
153 #return nil if $? && $?.exitstatus != 0
151 diff
154 diff
152 end
155 end
153
156
154 def cat(path, identifier=nil)
157 def cat(path, identifier=nil)
155 cmd = "#{BZR_BIN} cat"
158 cmd = "#{BZR_BIN} cat"
156 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
159 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
157 cmd << " #{target(path)}"
160 cmd << " #{target(path)}"
158 cat = nil
161 cat = nil
159 shellout(cmd) do |io|
162 shellout(cmd) do |io|
160 io.binmode
163 io.binmode
161 cat = io.read
164 cat = io.read
162 end
165 end
163 return nil if $? && $?.exitstatus != 0
166 return nil if $? && $?.exitstatus != 0
164 cat
167 cat
165 end
168 end
166
169
167 def annotate(path, identifier=nil)
170 def annotate(path, identifier=nil)
168 cmd = "#{BZR_BIN} annotate --all"
171 cmd = "#{BZR_BIN} annotate --all"
169 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
172 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
170 cmd << " #{target(path)}"
173 cmd << " #{target(path)}"
171 blame = Annotate.new
174 blame = Annotate.new
172 shellout(cmd) do |io|
175 shellout(cmd) do |io|
173 author = nil
176 author = nil
174 identifier = nil
177 identifier = nil
175 io.each_line do |line|
178 io.each_line do |line|
176 next unless line =~ %r{^(\d+) ([^|]+)\| (.*)$}
179 next unless line =~ %r{^(\d+) ([^|]+)\| (.*)$}
177 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
180 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
178 end
181 end
179 end
182 end
180 return nil if $? && $?.exitstatus != 0
183 return nil if $? && $?.exitstatus != 0
181 blame
184 blame
182 end
185 end
183 end
186 end
184 end
187 end
185 end
188 end
186 end
189 end
@@ -1,362 +1,362
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 CvsAdapter < AbstractAdapter
23 class CvsAdapter < AbstractAdapter
24
24
25 # CVS executable name
25 # CVS executable name
26 CVS_BIN = "cvs"
26 CVS_BIN = "cvs"
27
27
28 # Guidelines for the input:
28 # Guidelines for the input:
29 # url -> the project-path, relative to the cvsroot (eg. module name)
29 # url -> the project-path, relative to the cvsroot (eg. module name)
30 # root_url -> the good old, sometimes damned, CVSROOT
30 # root_url -> the good old, sometimes damned, CVSROOT
31 # login -> unnecessary
31 # login -> unnecessary
32 # password -> unnecessary too
32 # password -> unnecessary too
33 def initialize(url, root_url=nil, login=nil, password=nil)
33 def initialize(url, root_url=nil, login=nil, password=nil)
34 @url = url
34 @url = url
35 @login = login if login && !login.empty?
35 @login = login if login && !login.empty?
36 @password = (password || "") if @login
36 @password = (password || "") if @login
37 #TODO: better Exception here (IllegalArgumentException)
37 #TODO: better Exception here (IllegalArgumentException)
38 raise CommandFailed if root_url.blank?
38 raise CommandFailed if root_url.blank?
39 @root_url = root_url
39 @root_url = root_url
40 end
40 end
41
41
42 def root_url
42 def root_url
43 @root_url
43 @root_url
44 end
44 end
45
45
46 def url
46 def url
47 @url
47 @url
48 end
48 end
49
49
50 def info
50 def info
51 logger.debug "<cvs> info"
51 logger.debug "<cvs> info"
52 Info.new({:root_url => @root_url, :lastrev => nil})
52 Info.new({:root_url => @root_url, :lastrev => nil})
53 end
53 end
54
54
55 def get_previous_revision(revision)
55 def get_previous_revision(revision)
56 CvsRevisionHelper.new(revision).prevRev
56 CvsRevisionHelper.new(revision).prevRev
57 end
57 end
58
58
59 # Returns an Entries collection
59 # Returns an Entries collection
60 # or nil if the given path doesn't exist in the repository
60 # or nil if the given path doesn't exist in the repository
61 # this method is used by the repository-browser (aka LIST)
61 # this method is used by the repository-browser (aka LIST)
62 def entries(path=nil, identifier=nil)
62 def entries(path=nil, identifier=nil)
63 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
63 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
64 path_with_project="#{url}#{with_leading_slash(path)}"
64 path_with_project="#{url}#{with_leading_slash(path)}"
65 entries = Entries.new
65 entries = Entries.new
66 cmd = "#{CVS_BIN} -d #{root_url} rls -e"
66 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rls -e"
67 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
67 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
68 cmd << " #{shell_quote path_with_project}"
68 cmd << " #{shell_quote path_with_project}"
69 shellout(cmd) do |io|
69 shellout(cmd) do |io|
70 io.each_line(){|line|
70 io.each_line(){|line|
71 fields=line.chop.split('/',-1)
71 fields=line.chop.split('/',-1)
72 logger.debug(">>InspectLine #{fields.inspect}")
72 logger.debug(">>InspectLine #{fields.inspect}")
73
73
74 if fields[0]!="D"
74 if fields[0]!="D"
75 entries << Entry.new({:name => fields[-5],
75 entries << Entry.new({:name => fields[-5],
76 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
76 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
77 :path => "#{path}/#{fields[-5]}",
77 :path => "#{path}/#{fields[-5]}",
78 :kind => 'file',
78 :kind => 'file',
79 :size => nil,
79 :size => nil,
80 :lastrev => Revision.new({
80 :lastrev => Revision.new({
81 :revision => fields[-4],
81 :revision => fields[-4],
82 :name => fields[-4],
82 :name => fields[-4],
83 :time => Time.parse(fields[-3]),
83 :time => Time.parse(fields[-3]),
84 :author => ''
84 :author => ''
85 })
85 })
86 })
86 })
87 else
87 else
88 entries << Entry.new({:name => fields[1],
88 entries << Entry.new({:name => fields[1],
89 :path => "#{path}/#{fields[1]}",
89 :path => "#{path}/#{fields[1]}",
90 :kind => 'dir',
90 :kind => 'dir',
91 :size => nil,
91 :size => nil,
92 :lastrev => nil
92 :lastrev => nil
93 })
93 })
94 end
94 end
95 }
95 }
96 end
96 end
97 return nil if $? && $?.exitstatus != 0
97 return nil if $? && $?.exitstatus != 0
98 entries.sort_by_name
98 entries.sort_by_name
99 end
99 end
100
100
101 STARTLOG="----------------------------"
101 STARTLOG="----------------------------"
102 ENDLOG ="============================================================================="
102 ENDLOG ="============================================================================="
103
103
104 # Returns all revisions found between identifier_from and identifier_to
104 # Returns all revisions found between identifier_from and identifier_to
105 # in the repository. both identifier have to be dates or nil.
105 # in the repository. both identifier have to be dates or nil.
106 # these method returns nothing but yield every result in block
106 # these method returns nothing but yield every result in block
107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
108 logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
108 logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
109
109
110 path_with_project="#{url}#{with_leading_slash(path)}"
110 path_with_project="#{url}#{with_leading_slash(path)}"
111 cmd = "#{CVS_BIN} -d #{root_url} rlog"
111 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rlog"
112 cmd << " -d\">#{time_to_cvstime(identifier_from)}\"" if identifier_from
112 cmd << " -d\">#{time_to_cvstime(identifier_from)}\"" if identifier_from
113 cmd << " #{shell_quote path_with_project}"
113 cmd << " #{shell_quote path_with_project}"
114 shellout(cmd) do |io|
114 shellout(cmd) do |io|
115 state="entry_start"
115 state="entry_start"
116
116
117 commit_log=String.new
117 commit_log=String.new
118 revision=nil
118 revision=nil
119 date=nil
119 date=nil
120 author=nil
120 author=nil
121 entry_path=nil
121 entry_path=nil
122 entry_name=nil
122 entry_name=nil
123 file_state=nil
123 file_state=nil
124 branch_map=nil
124 branch_map=nil
125
125
126 io.each_line() do |line|
126 io.each_line() do |line|
127
127
128 if state!="revision" && /^#{ENDLOG}/ =~ line
128 if state!="revision" && /^#{ENDLOG}/ =~ line
129 commit_log=String.new
129 commit_log=String.new
130 revision=nil
130 revision=nil
131 state="entry_start"
131 state="entry_start"
132 end
132 end
133
133
134 if state=="entry_start"
134 if state=="entry_start"
135 branch_map=Hash.new
135 branch_map=Hash.new
136 if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
136 if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
137 entry_path = normalize_cvs_path($1)
137 entry_path = normalize_cvs_path($1)
138 entry_name = normalize_path(File.basename($1))
138 entry_name = normalize_path(File.basename($1))
139 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
139 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
140 elsif /^head: (.+)$/ =~ line
140 elsif /^head: (.+)$/ =~ line
141 entry_headRev = $1 #unless entry.nil?
141 entry_headRev = $1 #unless entry.nil?
142 elsif /^symbolic names:/ =~ line
142 elsif /^symbolic names:/ =~ line
143 state="symbolic" #unless entry.nil?
143 state="symbolic" #unless entry.nil?
144 elsif /^#{STARTLOG}/ =~ line
144 elsif /^#{STARTLOG}/ =~ line
145 commit_log=String.new
145 commit_log=String.new
146 state="revision"
146 state="revision"
147 end
147 end
148 next
148 next
149 elsif state=="symbolic"
149 elsif state=="symbolic"
150 if /^(.*):\s(.*)/ =~ (line.strip)
150 if /^(.*):\s(.*)/ =~ (line.strip)
151 branch_map[$1]=$2
151 branch_map[$1]=$2
152 else
152 else
153 state="tags"
153 state="tags"
154 next
154 next
155 end
155 end
156 elsif state=="tags"
156 elsif state=="tags"
157 if /^#{STARTLOG}/ =~ line
157 if /^#{STARTLOG}/ =~ line
158 commit_log = ""
158 commit_log = ""
159 state="revision"
159 state="revision"
160 elsif /^#{ENDLOG}/ =~ line
160 elsif /^#{ENDLOG}/ =~ line
161 state="head"
161 state="head"
162 end
162 end
163 next
163 next
164 elsif state=="revision"
164 elsif state=="revision"
165 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
165 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
166 if revision
166 if revision
167
167
168 revHelper=CvsRevisionHelper.new(revision)
168 revHelper=CvsRevisionHelper.new(revision)
169 revBranch="HEAD"
169 revBranch="HEAD"
170
170
171 branch_map.each() do |branch_name,branch_point|
171 branch_map.each() do |branch_name,branch_point|
172 if revHelper.is_in_branch_with_symbol(branch_point)
172 if revHelper.is_in_branch_with_symbol(branch_point)
173 revBranch=branch_name
173 revBranch=branch_name
174 end
174 end
175 end
175 end
176
176
177 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
177 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
178
178
179 yield Revision.new({
179 yield Revision.new({
180 :time => date,
180 :time => date,
181 :author => author,
181 :author => author,
182 :message=>commit_log.chomp,
182 :message=>commit_log.chomp,
183 :paths => [{
183 :paths => [{
184 :revision => revision,
184 :revision => revision,
185 :branch=> revBranch,
185 :branch=> revBranch,
186 :path=>entry_path,
186 :path=>entry_path,
187 :name=>entry_name,
187 :name=>entry_name,
188 :kind=>'file',
188 :kind=>'file',
189 :action=>file_state
189 :action=>file_state
190 }]
190 }]
191 })
191 })
192 end
192 end
193
193
194 commit_log=String.new
194 commit_log=String.new
195 revision=nil
195 revision=nil
196
196
197 if /^#{ENDLOG}/ =~ line
197 if /^#{ENDLOG}/ =~ line
198 state="entry_start"
198 state="entry_start"
199 end
199 end
200 next
200 next
201 end
201 end
202
202
203 if /^branches: (.+)$/ =~ line
203 if /^branches: (.+)$/ =~ line
204 #TODO: version.branch = $1
204 #TODO: version.branch = $1
205 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
205 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
206 revision = $1
206 revision = $1
207 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
207 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
208 date = Time.parse($1)
208 date = Time.parse($1)
209 author = /author: ([^;]+)/.match(line)[1]
209 author = /author: ([^;]+)/.match(line)[1]
210 file_state = /state: ([^;]+)/.match(line)[1]
210 file_state = /state: ([^;]+)/.match(line)[1]
211 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
211 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
212 # useful for stats or something else
212 # useful for stats or something else
213 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
213 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
214 # unless linechanges.nil?
214 # unless linechanges.nil?
215 # version.line_plus = linechanges[1]
215 # version.line_plus = linechanges[1]
216 # version.line_minus = linechanges[2]
216 # version.line_minus = linechanges[2]
217 # else
217 # else
218 # version.line_plus = 0
218 # version.line_plus = 0
219 # version.line_minus = 0
219 # version.line_minus = 0
220 # end
220 # end
221 else
221 else
222 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
222 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
223 end
223 end
224 end
224 end
225 end
225 end
226 end
226 end
227 end
227 end
228
228
229 def diff(path, identifier_from, identifier_to=nil)
229 def diff(path, identifier_from, identifier_to=nil)
230 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
230 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
231 path_with_project="#{url}#{with_leading_slash(path)}"
231 path_with_project="#{url}#{with_leading_slash(path)}"
232 cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
232 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rdiff -u -r#{identifier_to.to_i} -r#{identifier_from.to_i} #{shell_quote path_with_project}"
233 diff = []
233 diff = []
234 shellout(cmd) do |io|
234 shellout(cmd) do |io|
235 io.each_line do |line|
235 io.each_line do |line|
236 diff << line
236 diff << line
237 end
237 end
238 end
238 end
239 return nil if $? && $?.exitstatus != 0
239 return nil if $? && $?.exitstatus != 0
240 diff
240 diff
241 end
241 end
242
242
243 def cat(path, identifier=nil)
243 def cat(path, identifier=nil)
244 identifier = (identifier) ? identifier : "HEAD"
244 identifier = (identifier) ? identifier : "HEAD"
245 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
245 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
246 path_with_project="#{url}#{with_leading_slash(path)}"
246 path_with_project="#{url}#{with_leading_slash(path)}"
247 cmd = "#{CVS_BIN} -d #{root_url} co"
247 cmd = "#{CVS_BIN} -d #{shell_quote root_url} co"
248 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
248 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
249 cmd << " -p #{shell_quote path_with_project}"
249 cmd << " -p #{shell_quote path_with_project}"
250 cat = nil
250 cat = nil
251 shellout(cmd) do |io|
251 shellout(cmd) do |io|
252 cat = io.read
252 cat = io.read
253 end
253 end
254 return nil if $? && $?.exitstatus != 0
254 return nil if $? && $?.exitstatus != 0
255 cat
255 cat
256 end
256 end
257
257
258 def annotate(path, identifier=nil)
258 def annotate(path, identifier=nil)
259 identifier = (identifier) ? identifier : "HEAD"
259 identifier = (identifier) ? identifier.to_i : "HEAD"
260 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
260 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
261 path_with_project="#{url}#{with_leading_slash(path)}"
261 path_with_project="#{url}#{with_leading_slash(path)}"
262 cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
262 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
263 blame = Annotate.new
263 blame = Annotate.new
264 shellout(cmd) do |io|
264 shellout(cmd) do |io|
265 io.each_line do |line|
265 io.each_line do |line|
266 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
266 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
267 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
267 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
268 end
268 end
269 end
269 end
270 return nil if $? && $?.exitstatus != 0
270 return nil if $? && $?.exitstatus != 0
271 blame
271 blame
272 end
272 end
273
273
274 private
274 private
275
275
276 # Returns the root url without the connexion string
276 # Returns the root url without the connexion string
277 # :pserver:anonymous@foo.bar:/path => /path
277 # :pserver:anonymous@foo.bar:/path => /path
278 # :ext:cvsservername:/path => /path
278 # :ext:cvsservername:/path => /path
279 def root_url_path
279 def root_url_path
280 root_url.to_s.gsub(/^:.+:\d*/, '')
280 root_url.to_s.gsub(/^:.+:\d*/, '')
281 end
281 end
282
282
283 # convert a date/time into the CVS-format
283 # convert a date/time into the CVS-format
284 def time_to_cvstime(time)
284 def time_to_cvstime(time)
285 return nil if time.nil?
285 return nil if time.nil?
286 unless time.kind_of? Time
286 unless time.kind_of? Time
287 time = Time.parse(time)
287 time = Time.parse(time)
288 end
288 end
289 return time.strftime("%Y-%m-%d %H:%M:%S")
289 return time.strftime("%Y-%m-%d %H:%M:%S")
290 end
290 end
291
291
292 def normalize_cvs_path(path)
292 def normalize_cvs_path(path)
293 normalize_path(path.gsub(/Attic\//,''))
293 normalize_path(path.gsub(/Attic\//,''))
294 end
294 end
295
295
296 def normalize_path(path)
296 def normalize_path(path)
297 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
297 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
298 end
298 end
299 end
299 end
300
300
301 class CvsRevisionHelper
301 class CvsRevisionHelper
302 attr_accessor :complete_rev, :revision, :base, :branchid
302 attr_accessor :complete_rev, :revision, :base, :branchid
303
303
304 def initialize(complete_rev)
304 def initialize(complete_rev)
305 @complete_rev = complete_rev
305 @complete_rev = complete_rev
306 parseRevision()
306 parseRevision()
307 end
307 end
308
308
309 def branchPoint
309 def branchPoint
310 return @base
310 return @base
311 end
311 end
312
312
313 def branchVersion
313 def branchVersion
314 if isBranchRevision
314 if isBranchRevision
315 return @base+"."+@branchid
315 return @base+"."+@branchid
316 end
316 end
317 return @base
317 return @base
318 end
318 end
319
319
320 def isBranchRevision
320 def isBranchRevision
321 !@branchid.nil?
321 !@branchid.nil?
322 end
322 end
323
323
324 def prevRev
324 def prevRev
325 unless @revision==0
325 unless @revision==0
326 return buildRevision(@revision-1)
326 return buildRevision(@revision-1)
327 end
327 end
328 return buildRevision(@revision)
328 return buildRevision(@revision)
329 end
329 end
330
330
331 def is_in_branch_with_symbol(branch_symbol)
331 def is_in_branch_with_symbol(branch_symbol)
332 bpieces=branch_symbol.split(".")
332 bpieces=branch_symbol.split(".")
333 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
333 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
334 return (branchVersion==branch_start)
334 return (branchVersion==branch_start)
335 end
335 end
336
336
337 private
337 private
338 def buildRevision(rev)
338 def buildRevision(rev)
339 if rev== 0
339 if rev== 0
340 @base
340 @base
341 elsif @branchid.nil?
341 elsif @branchid.nil?
342 @base+"."+rev.to_s
342 @base+"."+rev.to_s
343 else
343 else
344 @base+"."+@branchid+"."+rev.to_s
344 @base+"."+@branchid+"."+rev.to_s
345 end
345 end
346 end
346 end
347
347
348 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
348 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
349 def parseRevision()
349 def parseRevision()
350 pieces=@complete_rev.split(".")
350 pieces=@complete_rev.split(".")
351 @revision=pieces.last.to_i
351 @revision=pieces.last.to_i
352 baseSize=1
352 baseSize=1
353 baseSize+=(pieces.size/2)
353 baseSize+=(pieces.size/2)
354 @base=pieces[0..-baseSize].join(".")
354 @base=pieces[0..-baseSize].join(".")
355 if baseSize > 2
355 if baseSize > 2
356 @branchid=pieces[-2]
356 @branchid=pieces[-2]
357 end
357 end
358 end
358 end
359 end
359 end
360 end
360 end
361 end
361 end
362 end
362 end
@@ -1,196 +1,196
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 require 'rexml/document'
19 require 'rexml/document'
20
20
21 module Redmine
21 module Redmine
22 module Scm
22 module Scm
23 module Adapters
23 module Adapters
24 class DarcsAdapter < AbstractAdapter
24 class DarcsAdapter < AbstractAdapter
25 # Darcs executable name
25 # Darcs executable name
26 DARCS_BIN = "darcs"
26 DARCS_BIN = "darcs"
27
27
28 class << self
28 class << self
29 def client_version
29 def client_version
30 @@client_version ||= (darcs_binary_version || [])
30 @@client_version ||= (darcs_binary_version || [])
31 end
31 end
32
32
33 def darcs_binary_version
33 def darcs_binary_version
34 cmd = "#{DARCS_BIN} --version"
34 cmd = "#{DARCS_BIN} --version"
35 version = nil
35 version = nil
36 shellout(cmd) do |io|
36 shellout(cmd) do |io|
37 # Read darcs version in first returned line
37 # Read darcs version in first returned line
38 if m = io.gets.match(%r{((\d+\.)+\d+)})
38 if m = io.gets.match(%r{((\d+\.)+\d+)})
39 version = m[0].scan(%r{\d+}).collect(&:to_i)
39 version = m[0].scan(%r{\d+}).collect(&:to_i)
40 end
40 end
41 end
41 end
42 return nil if $? && $?.exitstatus != 0
42 return nil if $? && $?.exitstatus != 0
43 version
43 version
44 end
44 end
45 end
45 end
46
46
47 def initialize(url, root_url=nil, login=nil, password=nil)
47 def initialize(url, root_url=nil, login=nil, password=nil)
48 @url = url
48 @url = url
49 @root_url = url
49 @root_url = url
50 end
50 end
51
51
52 def supports_cat?
52 def supports_cat?
53 # cat supported in darcs 2.0.0 and higher
53 # cat supported in darcs 2.0.0 and higher
54 self.class.client_version_above?([2, 0, 0])
54 self.class.client_version_above?([2, 0, 0])
55 end
55 end
56
56
57 # Get info about the darcs repository
57 # Get info about the darcs repository
58 def info
58 def info
59 rev = revisions(nil,nil,nil,{:limit => 1})
59 rev = revisions(nil,nil,nil,{:limit => 1})
60 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
60 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
61 end
61 end
62
62
63 # Returns an Entries collection
63 # Returns an Entries collection
64 # or nil if the given path doesn't exist in the repository
64 # or nil if the given path doesn't exist in the repository
65 def entries(path=nil, identifier=nil)
65 def entries(path=nil, identifier=nil)
66 path_prefix = (path.blank? ? '' : "#{path}/")
66 path_prefix = (path.blank? ? '' : "#{path}/")
67 path = '.' if path.blank?
67 path = '.' if path.blank?
68 entries = Entries.new
68 entries = Entries.new
69 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output"
69 cmd = "#{DARCS_BIN} annotate --repodir #{shell_quote @url} --xml-output"
70 cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
70 cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
71 cmd << " #{shell_quote path}"
71 cmd << " #{shell_quote path}"
72 shellout(cmd) do |io|
72 shellout(cmd) do |io|
73 begin
73 begin
74 doc = REXML::Document.new(io)
74 doc = REXML::Document.new(io)
75 if doc.root.name == 'directory'
75 if doc.root.name == 'directory'
76 doc.elements.each('directory/*') do |element|
76 doc.elements.each('directory/*') do |element|
77 next unless ['file', 'directory'].include? element.name
77 next unless ['file', 'directory'].include? element.name
78 entries << entry_from_xml(element, path_prefix)
78 entries << entry_from_xml(element, path_prefix)
79 end
79 end
80 elsif doc.root.name == 'file'
80 elsif doc.root.name == 'file'
81 entries << entry_from_xml(doc.root, path_prefix)
81 entries << entry_from_xml(doc.root, path_prefix)
82 end
82 end
83 rescue
83 rescue
84 end
84 end
85 end
85 end
86 return nil if $? && $?.exitstatus != 0
86 return nil if $? && $?.exitstatus != 0
87 entries.compact.sort_by_name
87 entries.compact.sort_by_name
88 end
88 end
89
89
90 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
90 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
91 path = '.' if path.blank?
91 path = '.' if path.blank?
92 revisions = Revisions.new
92 revisions = Revisions.new
93 cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
93 cmd = "#{DARCS_BIN} changes --repodir #{shell_quote @url} --xml-output"
94 cmd << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from
94 cmd << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from
95 cmd << " --last #{options[:limit].to_i}" if options[:limit]
95 cmd << " --last #{options[:limit].to_i}" if options[:limit]
96 shellout(cmd) do |io|
96 shellout(cmd) do |io|
97 begin
97 begin
98 doc = REXML::Document.new(io)
98 doc = REXML::Document.new(io)
99 doc.elements.each("changelog/patch") do |patch|
99 doc.elements.each("changelog/patch") do |patch|
100 message = patch.elements['name'].text
100 message = patch.elements['name'].text
101 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
101 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
102 revisions << Revision.new({:identifier => nil,
102 revisions << Revision.new({:identifier => nil,
103 :author => patch.attributes['author'],
103 :author => patch.attributes['author'],
104 :scmid => patch.attributes['hash'],
104 :scmid => patch.attributes['hash'],
105 :time => Time.parse(patch.attributes['local_date']),
105 :time => Time.parse(patch.attributes['local_date']),
106 :message => message,
106 :message => message,
107 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
107 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
108 })
108 })
109 end
109 end
110 rescue
110 rescue
111 end
111 end
112 end
112 end
113 return nil if $? && $?.exitstatus != 0
113 return nil if $? && $?.exitstatus != 0
114 revisions
114 revisions
115 end
115 end
116
116
117 def diff(path, identifier_from, identifier_to=nil)
117 def diff(path, identifier_from, identifier_to=nil)
118 path = '*' if path.blank?
118 path = '*' if path.blank?
119 cmd = "#{DARCS_BIN} diff --repodir #{@url}"
119 cmd = "#{DARCS_BIN} diff --repodir #{shell_quote @url}"
120 if identifier_to.nil?
120 if identifier_to.nil?
121 cmd << " --match #{shell_quote("hash #{identifier_from}")}"
121 cmd << " --match #{shell_quote("hash #{identifier_from}")}"
122 else
122 else
123 cmd << " --to-match #{shell_quote("hash #{identifier_from}")}"
123 cmd << " --to-match #{shell_quote("hash #{identifier_from}")}"
124 cmd << " --from-match #{shell_quote("hash #{identifier_to}")}"
124 cmd << " --from-match #{shell_quote("hash #{identifier_to}")}"
125 end
125 end
126 cmd << " -u #{shell_quote path}"
126 cmd << " -u #{shell_quote path}"
127 diff = []
127 diff = []
128 shellout(cmd) do |io|
128 shellout(cmd) do |io|
129 io.each_line do |line|
129 io.each_line do |line|
130 diff << line
130 diff << line
131 end
131 end
132 end
132 end
133 return nil if $? && $?.exitstatus != 0
133 return nil if $? && $?.exitstatus != 0
134 diff
134 diff
135 end
135 end
136
136
137 def cat(path, identifier=nil)
137 def cat(path, identifier=nil)
138 cmd = "#{DARCS_BIN} show content --repodir #{@url}"
138 cmd = "#{DARCS_BIN} show content --repodir #{shell_quote @url}"
139 cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
139 cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
140 cmd << " #{shell_quote path}"
140 cmd << " #{shell_quote path}"
141 cat = nil
141 cat = nil
142 shellout(cmd) do |io|
142 shellout(cmd) do |io|
143 io.binmode
143 io.binmode
144 cat = io.read
144 cat = io.read
145 end
145 end
146 return nil if $? && $?.exitstatus != 0
146 return nil if $? && $?.exitstatus != 0
147 cat
147 cat
148 end
148 end
149
149
150 private
150 private
151
151
152 # Returns an Entry from the given XML element
152 # Returns an Entry from the given XML element
153 # or nil if the entry was deleted
153 # or nil if the entry was deleted
154 def entry_from_xml(element, path_prefix)
154 def entry_from_xml(element, path_prefix)
155 modified_element = element.elements['modified']
155 modified_element = element.elements['modified']
156 if modified_element.elements['modified_how'].text.match(/removed/)
156 if modified_element.elements['modified_how'].text.match(/removed/)
157 return nil
157 return nil
158 end
158 end
159
159
160 Entry.new({:name => element.attributes['name'],
160 Entry.new({:name => element.attributes['name'],
161 :path => path_prefix + element.attributes['name'],
161 :path => path_prefix + element.attributes['name'],
162 :kind => element.name == 'file' ? 'file' : 'dir',
162 :kind => element.name == 'file' ? 'file' : 'dir',
163 :size => nil,
163 :size => nil,
164 :lastrev => Revision.new({
164 :lastrev => Revision.new({
165 :identifier => nil,
165 :identifier => nil,
166 :scmid => modified_element.elements['patch'].attributes['hash']
166 :scmid => modified_element.elements['patch'].attributes['hash']
167 })
167 })
168 })
168 })
169 end
169 end
170
170
171 # Retrieve changed paths for a single patch
171 # Retrieve changed paths for a single patch
172 def get_paths_for_patch(hash)
172 def get_paths_for_patch(hash)
173 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
173 cmd = "#{DARCS_BIN} annotate --repodir #{shell_quote @url} --summary --xml-output"
174 cmd << " --match #{shell_quote("hash #{hash}")} "
174 cmd << " --match #{shell_quote("hash #{hash}")} "
175 paths = []
175 paths = []
176 shellout(cmd) do |io|
176 shellout(cmd) do |io|
177 begin
177 begin
178 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
178 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
179 # A root element is added so that REXML doesn't raise an error
179 # A root element is added so that REXML doesn't raise an error
180 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
180 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
181 doc.elements.each('fake_root/summary/*') do |modif|
181 doc.elements.each('fake_root/summary/*') do |modif|
182 paths << {:action => modif.name[0,1].upcase,
182 paths << {:action => modif.name[0,1].upcase,
183 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
183 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
184 }
184 }
185 end
185 end
186 rescue
186 rescue
187 end
187 end
188 end
188 end
189 paths
189 paths
190 rescue CommandFailed
190 rescue CommandFailed
191 paths
191 paths
192 end
192 end
193 end
193 end
194 end
194 end
195 end
195 end
196 end
196 end
@@ -1,270 +1,270
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 GitAdapter < AbstractAdapter
23 class GitAdapter < AbstractAdapter
24 # Git executable name
24 # Git executable name
25 GIT_BIN = "git"
25 GIT_BIN = "git"
26
26
27 def info
27 def info
28 begin
28 begin
29 Info.new(:root_url => url, :lastrev => lastrev('',nil))
29 Info.new(:root_url => url, :lastrev => lastrev('',nil))
30 rescue
30 rescue
31 nil
31 nil
32 end
32 end
33 end
33 end
34
34
35 def branches
35 def branches
36 return @branches if @branches
36 return @branches if @branches
37 @branches = []
37 @branches = []
38 cmd = "#{GIT_BIN} --git-dir #{target('')} branch --no-color"
38 cmd = "#{GIT_BIN} --git-dir #{target('')} branch --no-color"
39 shellout(cmd) do |io|
39 shellout(cmd) do |io|
40 io.each_line do |line|
40 io.each_line do |line|
41 @branches << line.match('\s*\*?\s*(.*)$')[1]
41 @branches << line.match('\s*\*?\s*(.*)$')[1]
42 end
42 end
43 end
43 end
44 @branches.sort!
44 @branches.sort!
45 end
45 end
46
46
47 def tags
47 def tags
48 return @tags if @tags
48 return @tags if @tags
49 cmd = "#{GIT_BIN} --git-dir #{target('')} tag"
49 cmd = "#{GIT_BIN} --git-dir #{target('')} tag"
50 shellout(cmd) do |io|
50 shellout(cmd) do |io|
51 @tags = io.readlines.sort!.map{|t| t.strip}
51 @tags = io.readlines.sort!.map{|t| t.strip}
52 end
52 end
53 end
53 end
54
54
55 def default_branch
55 def default_branch
56 branches.include?('master') ? 'master' : branches.first
56 branches.include?('master') ? 'master' : branches.first
57 end
57 end
58
58
59 def entries(path=nil, identifier=nil)
59 def entries(path=nil, identifier=nil)
60 path ||= ''
60 path ||= ''
61 entries = Entries.new
61 entries = Entries.new
62 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
62 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
63 cmd << shell_quote("HEAD:" + path) if identifier.nil?
63 cmd << shell_quote("HEAD:" + path) if identifier.nil?
64 cmd << shell_quote(identifier + ":" + path) if identifier
64 cmd << shell_quote(identifier + ":" + path) if identifier
65 shellout(cmd) do |io|
65 shellout(cmd) do |io|
66 io.each_line do |line|
66 io.each_line do |line|
67 e = line.chomp.to_s
67 e = line.chomp.to_s
68 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
68 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
69 type = $1
69 type = $1
70 sha = $2
70 sha = $2
71 size = $3
71 size = $3
72 name = $4
72 name = $4
73 full_path = path.empty? ? name : "#{path}/#{name}"
73 full_path = path.empty? ? name : "#{path}/#{name}"
74 entries << Entry.new({:name => name,
74 entries << Entry.new({:name => name,
75 :path => full_path,
75 :path => full_path,
76 :kind => (type == "tree") ? 'dir' : 'file',
76 :kind => (type == "tree") ? 'dir' : 'file',
77 :size => (type == "tree") ? nil : size,
77 :size => (type == "tree") ? nil : size,
78 :lastrev => lastrev(full_path,identifier)
78 :lastrev => lastrev(full_path,identifier)
79 }) unless entries.detect{|entry| entry.name == name}
79 }) unless entries.detect{|entry| entry.name == name}
80 end
80 end
81 end
81 end
82 end
82 end
83 return nil if $? && $?.exitstatus != 0
83 return nil if $? && $?.exitstatus != 0
84 entries.sort_by_name
84 entries.sort_by_name
85 end
85 end
86
86
87 def lastrev(path,rev)
87 def lastrev(path,rev)
88 return nil if path.nil?
88 return nil if path.nil?
89 cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --date=iso --pretty=fuller --no-merges -n 1 "
89 cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --date=iso --pretty=fuller --no-merges -n 1 "
90 cmd << " #{shell_quote rev} " if rev
90 cmd << " #{shell_quote rev} " if rev
91 cmd << "-- #{shell_quote path} " unless path.empty?
91 cmd << "-- #{shell_quote path} " unless path.empty?
92 shellout(cmd) do |io|
92 shellout(cmd) do |io|
93 begin
93 begin
94 id = io.gets.split[1]
94 id = io.gets.split[1]
95 author = io.gets.match('Author:\s+(.*)$')[1]
95 author = io.gets.match('Author:\s+(.*)$')[1]
96 2.times { io.gets }
96 2.times { io.gets }
97 time = Time.parse(io.gets.match('CommitDate:\s+(.*)$')[1]).localtime
97 time = Time.parse(io.gets.match('CommitDate:\s+(.*)$')[1]).localtime
98
98
99 Revision.new({
99 Revision.new({
100 :identifier => id,
100 :identifier => id,
101 :scmid => id,
101 :scmid => id,
102 :author => author,
102 :author => author,
103 :time => time,
103 :time => time,
104 :message => nil,
104 :message => nil,
105 :paths => nil
105 :paths => nil
106 })
106 })
107 rescue NoMethodError => e
107 rescue NoMethodError => e
108 logger.error("The revision '#{path}' has a wrong format")
108 logger.error("The revision '#{path}' has a wrong format")
109 return nil
109 return nil
110 end
110 end
111 end
111 end
112 end
112 end
113
113
114 def revisions(path, identifier_from, identifier_to, options={})
114 def revisions(path, identifier_from, identifier_to, options={})
115 revisions = Revisions.new
115 revisions = Revisions.new
116
116
117 cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --raw --date=iso --pretty=fuller "
117 cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --raw --date=iso --pretty=fuller "
118 cmd << " --reverse " if options[:reverse]
118 cmd << " --reverse " if options[:reverse]
119 cmd << " --all " if options[:all]
119 cmd << " --all " if options[:all]
120 cmd << " -n #{options[:limit]} " if options[:limit]
120 cmd << " -n #{options[:limit].to_i} " if options[:limit]
121 cmd << "#{shell_quote(identifier_from + '..')}" if identifier_from
121 cmd << "#{shell_quote(identifier_from + '..')}" if identifier_from
122 cmd << "#{shell_quote identifier_to}" if identifier_to
122 cmd << "#{shell_quote identifier_to}" if identifier_to
123 cmd << " --since=#{shell_quote(options[:since].strftime("%Y-%m-%d %H:%M:%S"))}" if options[:since]
123 cmd << " --since=#{shell_quote(options[:since].strftime("%Y-%m-%d %H:%M:%S"))}" if options[:since]
124 cmd << " -- #{shell_quote path}" if path && !path.empty?
124 cmd << " -- #{shell_quote path}" if path && !path.empty?
125
125
126 shellout(cmd) do |io|
126 shellout(cmd) do |io|
127 files=[]
127 files=[]
128 changeset = {}
128 changeset = {}
129 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
129 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
130 revno = 1
130 revno = 1
131
131
132 io.each_line do |line|
132 io.each_line do |line|
133 if line =~ /^commit ([0-9a-f]{40})$/
133 if line =~ /^commit ([0-9a-f]{40})$/
134 key = "commit"
134 key = "commit"
135 value = $1
135 value = $1
136 if (parsing_descr == 1 || parsing_descr == 2)
136 if (parsing_descr == 1 || parsing_descr == 2)
137 parsing_descr = 0
137 parsing_descr = 0
138 revision = Revision.new({
138 revision = Revision.new({
139 :identifier => changeset[:commit],
139 :identifier => changeset[:commit],
140 :scmid => changeset[:commit],
140 :scmid => changeset[:commit],
141 :author => changeset[:author],
141 :author => changeset[:author],
142 :time => Time.parse(changeset[:date]),
142 :time => Time.parse(changeset[:date]),
143 :message => changeset[:description],
143 :message => changeset[:description],
144 :paths => files
144 :paths => files
145 })
145 })
146 if block_given?
146 if block_given?
147 yield revision
147 yield revision
148 else
148 else
149 revisions << revision
149 revisions << revision
150 end
150 end
151 changeset = {}
151 changeset = {}
152 files = []
152 files = []
153 revno = revno + 1
153 revno = revno + 1
154 end
154 end
155 changeset[:commit] = $1
155 changeset[:commit] = $1
156 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
156 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
157 key = $1
157 key = $1
158 value = $2
158 value = $2
159 if key == "Author"
159 if key == "Author"
160 changeset[:author] = value
160 changeset[:author] = value
161 elsif key == "CommitDate"
161 elsif key == "CommitDate"
162 changeset[:date] = value
162 changeset[:date] = value
163 end
163 end
164 elsif (parsing_descr == 0) && line.chomp.to_s == ""
164 elsif (parsing_descr == 0) && line.chomp.to_s == ""
165 parsing_descr = 1
165 parsing_descr = 1
166 changeset[:description] = ""
166 changeset[:description] = ""
167 elsif (parsing_descr == 1 || parsing_descr == 2) \
167 elsif (parsing_descr == 1 || parsing_descr == 2) \
168 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/
168 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/
169 parsing_descr = 2
169 parsing_descr = 2
170 fileaction = $1
170 fileaction = $1
171 filepath = $2
171 filepath = $2
172 files << {:action => fileaction, :path => filepath}
172 files << {:action => fileaction, :path => filepath}
173 elsif (parsing_descr == 1 || parsing_descr == 2) \
173 elsif (parsing_descr == 1 || parsing_descr == 2) \
174 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/
174 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/
175 parsing_descr = 2
175 parsing_descr = 2
176 fileaction = $1
176 fileaction = $1
177 filepath = $3
177 filepath = $3
178 files << {:action => fileaction, :path => filepath}
178 files << {:action => fileaction, :path => filepath}
179 elsif (parsing_descr == 1) && line.chomp.to_s == ""
179 elsif (parsing_descr == 1) && line.chomp.to_s == ""
180 parsing_descr = 2
180 parsing_descr = 2
181 elsif (parsing_descr == 1)
181 elsif (parsing_descr == 1)
182 changeset[:description] << line[4..-1]
182 changeset[:description] << line[4..-1]
183 end
183 end
184 end
184 end
185
185
186 if changeset[:commit]
186 if changeset[:commit]
187 revision = Revision.new({
187 revision = Revision.new({
188 :identifier => changeset[:commit],
188 :identifier => changeset[:commit],
189 :scmid => changeset[:commit],
189 :scmid => changeset[:commit],
190 :author => changeset[:author],
190 :author => changeset[:author],
191 :time => Time.parse(changeset[:date]),
191 :time => Time.parse(changeset[:date]),
192 :message => changeset[:description],
192 :message => changeset[:description],
193 :paths => files
193 :paths => files
194 })
194 })
195
195
196 if block_given?
196 if block_given?
197 yield revision
197 yield revision
198 else
198 else
199 revisions << revision
199 revisions << revision
200 end
200 end
201 end
201 end
202 end
202 end
203
203
204 return nil if $? && $?.exitstatus != 0
204 return nil if $? && $?.exitstatus != 0
205 revisions
205 revisions
206 end
206 end
207
207
208 def diff(path, identifier_from, identifier_to=nil)
208 def diff(path, identifier_from, identifier_to=nil)
209 path ||= ''
209 path ||= ''
210
210
211 if identifier_to
211 if identifier_to
212 cmd = "#{GIT_BIN} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}"
212 cmd = "#{GIT_BIN} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}"
213 else
213 else
214 cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}"
214 cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}"
215 end
215 end
216
216
217 cmd << " -- #{shell_quote path}" unless path.empty?
217 cmd << " -- #{shell_quote path}" unless path.empty?
218 diff = []
218 diff = []
219 shellout(cmd) do |io|
219 shellout(cmd) do |io|
220 io.each_line do |line|
220 io.each_line do |line|
221 diff << line
221 diff << line
222 end
222 end
223 end
223 end
224 return nil if $? && $?.exitstatus != 0
224 return nil if $? && $?.exitstatus != 0
225 diff
225 diff
226 end
226 end
227
227
228 def annotate(path, identifier=nil)
228 def annotate(path, identifier=nil)
229 identifier = 'HEAD' if identifier.blank?
229 identifier = 'HEAD' if identifier.blank?
230 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}"
230 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}"
231 blame = Annotate.new
231 blame = Annotate.new
232 content = nil
232 content = nil
233 shellout(cmd) { |io| io.binmode; content = io.read }
233 shellout(cmd) { |io| io.binmode; content = io.read }
234 return nil if $? && $?.exitstatus != 0
234 return nil if $? && $?.exitstatus != 0
235 # git annotates binary files
235 # git annotates binary files
236 return nil if content.is_binary_data?
236 return nil if content.is_binary_data?
237 identifier = ''
237 identifier = ''
238 # git shows commit author on the first occurrence only
238 # git shows commit author on the first occurrence only
239 authors_by_commit = {}
239 authors_by_commit = {}
240 content.split("\n").each do |line|
240 content.split("\n").each do |line|
241 if line =~ /^([0-9a-f]{39,40})\s.*/
241 if line =~ /^([0-9a-f]{39,40})\s.*/
242 identifier = $1
242 identifier = $1
243 elsif line =~ /^author (.+)/
243 elsif line =~ /^author (.+)/
244 authors_by_commit[identifier] = $1.strip
244 authors_by_commit[identifier] = $1.strip
245 elsif line =~ /^\t(.*)/
245 elsif line =~ /^\t(.*)/
246 blame.add_line($1, Revision.new(:identifier => identifier, :author => authors_by_commit[identifier]))
246 blame.add_line($1, Revision.new(:identifier => identifier, :author => authors_by_commit[identifier]))
247 identifier = ''
247 identifier = ''
248 author = ''
248 author = ''
249 end
249 end
250 end
250 end
251 blame
251 blame
252 end
252 end
253
253
254 def cat(path, identifier=nil)
254 def cat(path, identifier=nil)
255 if identifier.nil?
255 if identifier.nil?
256 identifier = 'HEAD'
256 identifier = 'HEAD'
257 end
257 end
258 cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}"
258 cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}"
259 cat = nil
259 cat = nil
260 shellout(cmd) do |io|
260 shellout(cmd) do |io|
261 io.binmode
261 io.binmode
262 cat = io.read
262 cat = io.read
263 end
263 end
264 return nil if $? && $?.exitstatus != 0
264 return nil if $? && $?.exitstatus != 0
265 cat
265 cat
266 end
266 end
267 end
267 end
268 end
268 end
269 end
269 end
270 end
270 end
@@ -1,205 +1,208
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 TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial"
27 TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial"
28 TEMPLATE_NAME = "hg-template"
28 TEMPLATE_NAME = "hg-template"
29 TEMPLATE_EXTENSION = "tmpl"
29 TEMPLATE_EXTENSION = "tmpl"
30
30
31 class << self
31 class << self
32 def client_version
32 def client_version
33 @@client_version ||= (hgversion || [])
33 @@client_version ||= (hgversion || [])
34 end
34 end
35
35
36 def hgversion
36 def hgversion
37 # The hg version is expressed either as a
37 # The hg version is expressed either as a
38 # release number (eg 0.9.5 or 1.0) or as a revision
38 # release number (eg 0.9.5 or 1.0) or as a revision
39 # id composed of 12 hexa characters.
39 # id composed of 12 hexa characters.
40 theversion = hgversion_from_command_line
40 theversion = hgversion_from_command_line
41 if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
41 if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
42 m[2].scan(%r{\d+}).collect(&:to_i)
42 m[2].scan(%r{\d+}).collect(&:to_i)
43 end
43 end
44 end
44 end
45
45
46 def hgversion_from_command_line
46 def hgversion_from_command_line
47 shellout("#{HG_BIN} --version") { |io| io.read }.to_s
47 shellout("#{HG_BIN} --version") { |io| io.read }.to_s
48 end
48 end
49
49
50 def template_path
50 def template_path
51 @@template_path ||= template_path_for(client_version)
51 @@template_path ||= template_path_for(client_version)
52 end
52 end
53
53
54 def template_path_for(version)
54 def template_path_for(version)
55 if ((version <=> [0,9,5]) > 0) || version.empty?
55 if ((version <=> [0,9,5]) > 0) || version.empty?
56 ver = "1.0"
56 ver = "1.0"
57 else
57 else
58 ver = "0.9.5"
58 ver = "0.9.5"
59 end
59 end
60 "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
60 "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
61 end
61 end
62 end
62 end
63
63
64 def info
64 def info
65 cmd = "#{HG_BIN} -R #{target('')} root"
65 cmd = "#{HG_BIN} -R #{target('')} root"
66 root_url = nil
66 root_url = nil
67 shellout(cmd) do |io|
67 shellout(cmd) do |io|
68 root_url = io.read
68 root_url = io.read
69 end
69 end
70 return nil if $? && $?.exitstatus != 0
70 return nil if $? && $?.exitstatus != 0
71 info = Info.new({:root_url => root_url.chomp,
71 info = Info.new({:root_url => root_url.chomp,
72 :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
72 :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
73 })
73 })
74 info
74 info
75 rescue CommandFailed
75 rescue CommandFailed
76 return nil
76 return nil
77 end
77 end
78
78
79 def entries(path=nil, identifier=nil)
79 def entries(path=nil, identifier=nil)
80 path ||= ''
80 path ||= ''
81 entries = Entries.new
81 entries = Entries.new
82 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
82 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
83 cmd << " -r " + (identifier ? identifier.to_s : "tip")
83 cmd << " -r " + shell_quote(identifier ? identifier.to_s : "tip")
84 cmd << " " + shell_quote("path:#{path}") unless path.empty?
84 cmd << " " + shell_quote("path:#{path}") unless path.empty?
85 shellout(cmd) do |io|
85 shellout(cmd) do |io|
86 io.each_line do |line|
86 io.each_line do |line|
87 # HG uses antislashs as separator on Windows
87 # HG uses antislashs as separator on Windows
88 line = line.gsub(/\\/, "/")
88 line = line.gsub(/\\/, "/")
89 if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'')
89 if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'')
90 e ||= line
90 e ||= line
91 e = e.chomp.split(%r{[\/\\]})
91 e = e.chomp.split(%r{[\/\\]})
92 entries << Entry.new({:name => e.first,
92 entries << Entry.new({:name => e.first,
93 :path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"),
93 :path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"),
94 :kind => (e.size > 1 ? 'dir' : 'file'),
94 :kind => (e.size > 1 ? 'dir' : 'file'),
95 :lastrev => Revision.new
95 :lastrev => Revision.new
96 }) unless e.empty? || entries.detect{|entry| entry.name == e.first}
96 }) unless e.empty? || entries.detect{|entry| entry.name == e.first}
97 end
97 end
98 end
98 end
99 end
99 end
100 return nil if $? && $?.exitstatus != 0
100 return nil if $? && $?.exitstatus != 0
101 entries.sort_by_name
101 entries.sort_by_name
102 end
102 end
103
103
104 # Fetch the revisions by using a template file that
104 # Fetch the revisions by using a template file that
105 # makes Mercurial produce a xml output.
105 # makes Mercurial produce a xml output.
106 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
106 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
107 revisions = Revisions.new
107 revisions = Revisions.new
108 cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}"
108 cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}"
109 if identifier_from && identifier_to
109 if identifier_from && identifier_to
110 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
110 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
111 elsif identifier_from
111 elsif identifier_from
112 cmd << " -r #{identifier_from.to_i}:"
112 cmd << " -r #{identifier_from.to_i}:"
113 end
113 end
114 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
114 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
115 cmd << " #{path}" if path
115 cmd << " #{shell_quote path}" if path
116 shellout(cmd) do |io|
116 shellout(cmd) do |io|
117 begin
117 begin
118 # HG doesn't close the XML Document...
118 # HG doesn't close the XML Document...
119 doc = REXML::Document.new(io.read << "</log>")
119 doc = REXML::Document.new(io.read << "</log>")
120 doc.elements.each("log/logentry") do |logentry|
120 doc.elements.each("log/logentry") do |logentry|
121 paths = []
121 paths = []
122 copies = logentry.get_elements('paths/path-copied')
122 copies = logentry.get_elements('paths/path-copied')
123 logentry.elements.each("paths/path") do |path|
123 logentry.elements.each("paths/path") do |path|
124 # Detect if the added file is a copy
124 # Detect if the added file is a copy
125 if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text }
125 if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text }
126 from_path = c.attributes['copyfrom-path']
126 from_path = c.attributes['copyfrom-path']
127 from_rev = logentry.attributes['revision']
127 from_rev = logentry.attributes['revision']
128 end
128 end
129 paths << {:action => path.attributes['action'],
129 paths << {:action => path.attributes['action'],
130 :path => "/#{path.text}",
130 :path => "/#{path.text}",
131 :from_path => from_path ? "/#{from_path}" : nil,
131 :from_path => from_path ? "/#{from_path}" : nil,
132 :from_revision => from_rev ? from_rev : nil
132 :from_revision => from_rev ? from_rev : nil
133 }
133 }
134 end
134 end
135 paths.sort! { |x,y| x[:path] <=> y[:path] }
135 paths.sort! { |x,y| x[:path] <=> y[:path] }
136
136
137 revisions << Revision.new({:identifier => logentry.attributes['revision'],
137 revisions << Revision.new({:identifier => logentry.attributes['revision'],
138 :scmid => logentry.attributes['node'],
138 :scmid => logentry.attributes['node'],
139 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
139 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
140 :time => Time.parse(logentry.elements['date'].text).localtime,
140 :time => Time.parse(logentry.elements['date'].text).localtime,
141 :message => logentry.elements['msg'].text,
141 :message => logentry.elements['msg'].text,
142 :paths => paths
142 :paths => paths
143 })
143 })
144 end
144 end
145 rescue
145 rescue
146 logger.debug($!)
146 logger.debug($!)
147 end
147 end
148 end
148 end
149 return nil if $? && $?.exitstatus != 0
149 return nil if $? && $?.exitstatus != 0
150 revisions
150 revisions
151 end
151 end
152
152
153 def diff(path, identifier_from, identifier_to=nil)
153 def diff(path, identifier_from, identifier_to=nil)
154 path ||= ''
154 path ||= ''
155 if identifier_to
155 if identifier_to
156 identifier_to = identifier_to.to_i
156 identifier_to = identifier_to.to_i
157 else
157 else
158 identifier_to = identifier_from.to_i - 1
158 identifier_to = identifier_from.to_i - 1
159 end
159 end
160 if identifier_from
161 identifier_from = identifier_from.to_i
162 end
160 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
163 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
161 cmd << " -I #{target(path)}" unless path.empty?
164 cmd << " -I #{target(path)}" unless path.empty?
162 diff = []
165 diff = []
163 shellout(cmd) do |io|
166 shellout(cmd) do |io|
164 io.each_line do |line|
167 io.each_line do |line|
165 diff << line
168 diff << line
166 end
169 end
167 end
170 end
168 return nil if $? && $?.exitstatus != 0
171 return nil if $? && $?.exitstatus != 0
169 diff
172 diff
170 end
173 end
171
174
172 def cat(path, identifier=nil)
175 def cat(path, identifier=nil)
173 cmd = "#{HG_BIN} -R #{target('')} cat"
176 cmd = "#{HG_BIN} -R #{target('')} cat"
174 cmd << " -r " + (identifier ? identifier.to_s : "tip")
177 cmd << " -r " + shell_quote(identifier ? identifier.to_s : "tip")
175 cmd << " #{target(path)}"
178 cmd << " #{target(path)}"
176 cat = nil
179 cat = nil
177 shellout(cmd) do |io|
180 shellout(cmd) do |io|
178 io.binmode
181 io.binmode
179 cat = io.read
182 cat = io.read
180 end
183 end
181 return nil if $? && $?.exitstatus != 0
184 return nil if $? && $?.exitstatus != 0
182 cat
185 cat
183 end
186 end
184
187
185 def annotate(path, identifier=nil)
188 def annotate(path, identifier=nil)
186 path ||= ''
189 path ||= ''
187 cmd = "#{HG_BIN} -R #{target('')}"
190 cmd = "#{HG_BIN} -R #{target('')}"
188 cmd << " annotate -n -u"
191 cmd << " annotate -n -u"
189 cmd << " -r " + (identifier ? identifier.to_s : "tip")
192 cmd << " -r " + shell_quote(identifier ? identifier.to_s : "tip")
190 cmd << " -r #{identifier.to_i}" if identifier
193 cmd << " -r #{identifier.to_i}" if identifier
191 cmd << " #{target(path)}"
194 cmd << " #{target(path)}"
192 blame = Annotate.new
195 blame = Annotate.new
193 shellout(cmd) do |io|
196 shellout(cmd) do |io|
194 io.each_line do |line|
197 io.each_line do |line|
195 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
198 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
196 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
199 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
197 end
200 end
198 end
201 end
199 return nil if $? && $?.exitstatus != 0
202 return nil if $? && $?.exitstatus != 0
200 blame
203 blame
201 end
204 end
202 end
205 end
203 end
206 end
204 end
207 end
205 end
208 end
@@ -1,256 +1,256
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
2 # Copyright (C) 2006-2010 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 require 'uri'
19 require 'uri'
20
20
21 module Redmine
21 module Redmine
22 module Scm
22 module Scm
23 module Adapters
23 module Adapters
24 class SubversionAdapter < AbstractAdapter
24 class SubversionAdapter < AbstractAdapter
25
25
26 # SVN executable name
26 # SVN executable name
27 SVN_BIN = "svn"
27 SVN_BIN = "svn"
28
28
29 class << self
29 class << self
30 def client_version
30 def client_version
31 @@client_version ||= (svn_binary_version || [])
31 @@client_version ||= (svn_binary_version || [])
32 end
32 end
33
33
34 def svn_binary_version
34 def svn_binary_version
35 cmd = "#{SVN_BIN} --version"
35 cmd = "#{SVN_BIN} --version"
36 version = nil
36 version = nil
37 shellout(cmd) do |io|
37 shellout(cmd) do |io|
38 # Read svn version in first returned line
38 # Read svn version in first returned line
39 if m = io.read.to_s.match(%r{\A(.*?)((\d+\.)+\d+)})
39 if m = io.read.to_s.match(%r{\A(.*?)((\d+\.)+\d+)})
40 version = m[2].scan(%r{\d+}).collect(&:to_i)
40 version = m[2].scan(%r{\d+}).collect(&:to_i)
41 end
41 end
42 end
42 end
43 return nil if $? && $?.exitstatus != 0
43 return nil if $? && $?.exitstatus != 0
44 version
44 version
45 end
45 end
46 end
46 end
47
47
48 # Get info about the svn repository
48 # Get info about the svn repository
49 def info
49 def info
50 cmd = "#{SVN_BIN} info --xml #{target}"
50 cmd = "#{SVN_BIN} info --xml #{target}"
51 cmd << credentials_string
51 cmd << credentials_string
52 info = nil
52 info = nil
53 shellout(cmd) do |io|
53 shellout(cmd) do |io|
54 output = io.read
54 output = io.read
55 begin
55 begin
56 doc = ActiveSupport::XmlMini.parse(output)
56 doc = ActiveSupport::XmlMini.parse(output)
57 #root_url = doc.elements["info/entry/repository/root"].text
57 #root_url = doc.elements["info/entry/repository/root"].text
58 info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'],
58 info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'],
59 :lastrev => Revision.new({
59 :lastrev => Revision.new({
60 :identifier => doc['info']['entry']['commit']['revision'],
60 :identifier => doc['info']['entry']['commit']['revision'],
61 :time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime,
61 :time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime,
62 :author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "")
62 :author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "")
63 })
63 })
64 })
64 })
65 rescue
65 rescue
66 end
66 end
67 end
67 end
68 return nil if $? && $?.exitstatus != 0
68 return nil if $? && $?.exitstatus != 0
69 info
69 info
70 rescue CommandFailed
70 rescue CommandFailed
71 return nil
71 return nil
72 end
72 end
73
73
74 # Returns an Entries collection
74 # Returns an Entries collection
75 # or nil if the given path doesn't exist in the repository
75 # or nil if the given path doesn't exist in the repository
76 def entries(path=nil, identifier=nil)
76 def entries(path=nil, identifier=nil)
77 path ||= ''
77 path ||= ''
78 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
78 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
79 entries = Entries.new
79 entries = Entries.new
80 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
80 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
81 cmd << credentials_string
81 cmd << credentials_string
82 shellout(cmd) do |io|
82 shellout(cmd) do |io|
83 output = io.read
83 output = io.read
84 begin
84 begin
85 doc = ActiveSupport::XmlMini.parse(output)
85 doc = ActiveSupport::XmlMini.parse(output)
86 each_xml_element(doc['lists']['list'], 'entry') do |entry|
86 each_xml_element(doc['lists']['list'], 'entry') do |entry|
87 commit = entry['commit']
87 commit = entry['commit']
88 commit_date = commit['date']
88 commit_date = commit['date']
89 # Skip directory if there is no commit date (usually that
89 # Skip directory if there is no commit date (usually that
90 # means that we don't have read access to it)
90 # means that we don't have read access to it)
91 next if entry['kind'] == 'dir' && commit_date.nil?
91 next if entry['kind'] == 'dir' && commit_date.nil?
92 name = entry['name']['__content__']
92 name = entry['name']['__content__']
93 entries << Entry.new({:name => URI.unescape(name),
93 entries << Entry.new({:name => URI.unescape(name),
94 :path => ((path.empty? ? "" : "#{path}/") + name),
94 :path => ((path.empty? ? "" : "#{path}/") + name),
95 :kind => entry['kind'],
95 :kind => entry['kind'],
96 :size => ((s = entry['size']) ? s['__content__'].to_i : nil),
96 :size => ((s = entry['size']) ? s['__content__'].to_i : nil),
97 :lastrev => Revision.new({
97 :lastrev => Revision.new({
98 :identifier => commit['revision'],
98 :identifier => commit['revision'],
99 :time => Time.parse(commit_date['__content__'].to_s).localtime,
99 :time => Time.parse(commit_date['__content__'].to_s).localtime,
100 :author => ((a = commit['author']) ? a['__content__'] : nil)
100 :author => ((a = commit['author']) ? a['__content__'] : nil)
101 })
101 })
102 })
102 })
103 end
103 end
104 rescue Exception => e
104 rescue Exception => e
105 logger.error("Error parsing svn output: #{e.message}")
105 logger.error("Error parsing svn output: #{e.message}")
106 logger.error("Output was:\n #{output}")
106 logger.error("Output was:\n #{output}")
107 end
107 end
108 end
108 end
109 return nil if $? && $?.exitstatus != 0
109 return nil if $? && $?.exitstatus != 0
110 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
110 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
111 entries.sort_by_name
111 entries.sort_by_name
112 end
112 end
113
113
114 def properties(path, identifier=nil)
114 def properties(path, identifier=nil)
115 # proplist xml output supported in svn 1.5.0 and higher
115 # proplist xml output supported in svn 1.5.0 and higher
116 return nil unless self.class.client_version_above?([1, 5, 0])
116 return nil unless self.class.client_version_above?([1, 5, 0])
117
117
118 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
118 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
119 cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}"
119 cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}"
120 cmd << credentials_string
120 cmd << credentials_string
121 properties = {}
121 properties = {}
122 shellout(cmd) do |io|
122 shellout(cmd) do |io|
123 output = io.read
123 output = io.read
124 begin
124 begin
125 doc = ActiveSupport::XmlMini.parse(output)
125 doc = ActiveSupport::XmlMini.parse(output)
126 each_xml_element(doc['properties']['target'], 'property') do |property|
126 each_xml_element(doc['properties']['target'], 'property') do |property|
127 properties[ property['name'] ] = property['__content__'].to_s
127 properties[ property['name'] ] = property['__content__'].to_s
128 end
128 end
129 rescue
129 rescue
130 end
130 end
131 end
131 end
132 return nil if $? && $?.exitstatus != 0
132 return nil if $? && $?.exitstatus != 0
133 properties
133 properties
134 end
134 end
135
135
136 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
136 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
137 path ||= ''
137 path ||= ''
138 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
138 identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
139 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
139 identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1
140 revisions = Revisions.new
140 revisions = Revisions.new
141 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
141 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
142 cmd << credentials_string
142 cmd << credentials_string
143 cmd << " --verbose " if options[:with_paths]
143 cmd << " --verbose " if options[:with_paths]
144 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
144 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
145 cmd << ' ' + target(path)
145 cmd << ' ' + target(path)
146 shellout(cmd) do |io|
146 shellout(cmd) do |io|
147 output = io.read
147 output = io.read
148 begin
148 begin
149 doc = ActiveSupport::XmlMini.parse(output)
149 doc = ActiveSupport::XmlMini.parse(output)
150 each_xml_element(doc['log'], 'logentry') do |logentry|
150 each_xml_element(doc['log'], 'logentry') do |logentry|
151 paths = []
151 paths = []
152 each_xml_element(logentry['paths'], 'path') do |path|
152 each_xml_element(logentry['paths'], 'path') do |path|
153 paths << {:action => path['action'],
153 paths << {:action => path['action'],
154 :path => path['__content__'],
154 :path => path['__content__'],
155 :from_path => path['copyfrom-path'],
155 :from_path => path['copyfrom-path'],
156 :from_revision => path['copyfrom-rev']
156 :from_revision => path['copyfrom-rev']
157 }
157 }
158 end if logentry['paths'] && logentry['paths']['path']
158 end if logentry['paths'] && logentry['paths']['path']
159 paths.sort! { |x,y| x[:path] <=> y[:path] }
159 paths.sort! { |x,y| x[:path] <=> y[:path] }
160
160
161 revisions << Revision.new({:identifier => logentry['revision'],
161 revisions << Revision.new({:identifier => logentry['revision'],
162 :author => (logentry['author'] ? logentry['author']['__content__'] : ""),
162 :author => (logentry['author'] ? logentry['author']['__content__'] : ""),
163 :time => Time.parse(logentry['date']['__content__'].to_s).localtime,
163 :time => Time.parse(logentry['date']['__content__'].to_s).localtime,
164 :message => logentry['msg']['__content__'],
164 :message => logentry['msg']['__content__'],
165 :paths => paths
165 :paths => paths
166 })
166 })
167 end
167 end
168 rescue
168 rescue
169 end
169 end
170 end
170 end
171 return nil if $? && $?.exitstatus != 0
171 return nil if $? && $?.exitstatus != 0
172 revisions
172 revisions
173 end
173 end
174
174
175 def diff(path, identifier_from, identifier_to=nil, type="inline")
175 def diff(path, identifier_from, identifier_to=nil, type="inline")
176 path ||= ''
176 path ||= ''
177 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
177 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
178 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
178 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
179
179
180 cmd = "#{SVN_BIN} diff -r "
180 cmd = "#{SVN_BIN} diff -r "
181 cmd << "#{identifier_to}:"
181 cmd << "#{identifier_to}:"
182 cmd << "#{identifier_from}"
182 cmd << "#{identifier_from}"
183 cmd << " #{target(path)}@#{identifier_from}"
183 cmd << " #{target(path)}@#{identifier_from}"
184 cmd << credentials_string
184 cmd << credentials_string
185 diff = []
185 diff = []
186 shellout(cmd) do |io|
186 shellout(cmd) do |io|
187 io.each_line do |line|
187 io.each_line do |line|
188 diff << line
188 diff << line
189 end
189 end
190 end
190 end
191 return nil if $? && $?.exitstatus != 0
191 return nil if $? && $?.exitstatus != 0
192 diff
192 diff
193 end
193 end
194
194
195 def cat(path, identifier=nil)
195 def cat(path, identifier=nil)
196 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
196 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
197 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
197 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
198 cmd << credentials_string
198 cmd << credentials_string
199 cat = nil
199 cat = nil
200 shellout(cmd) do |io|
200 shellout(cmd) do |io|
201 io.binmode
201 io.binmode
202 cat = io.read
202 cat = io.read
203 end
203 end
204 return nil if $? && $?.exitstatus != 0
204 return nil if $? && $?.exitstatus != 0
205 cat
205 cat
206 end
206 end
207
207
208 def annotate(path, identifier=nil)
208 def annotate(path, identifier=nil)
209 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
209 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
210 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
210 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
211 cmd << credentials_string
211 cmd << credentials_string
212 blame = Annotate.new
212 blame = Annotate.new
213 shellout(cmd) do |io|
213 shellout(cmd) do |io|
214 io.each_line do |line|
214 io.each_line do |line|
215 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
215 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
216 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
216 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
217 end
217 end
218 end
218 end
219 return nil if $? && $?.exitstatus != 0
219 return nil if $? && $?.exitstatus != 0
220 blame
220 blame
221 end
221 end
222
222
223 private
223 private
224
224
225 def credentials_string
225 def credentials_string
226 str = ''
226 str = ''
227 str << " --username #{shell_quote(@login)}" unless @login.blank?
227 str << " --username #{shell_quote(@login)}" unless @login.blank?
228 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
228 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
229 str << " --no-auth-cache --non-interactive"
229 str << " --no-auth-cache --non-interactive"
230 str
230 str
231 end
231 end
232
232
233 # Helper that iterates over the child elements of a xml node
233 # Helper that iterates over the child elements of a xml node
234 # MiniXml returns a hash when a single child is found or an array of hashes for multiple children
234 # MiniXml returns a hash when a single child is found or an array of hashes for multiple children
235 def each_xml_element(node, name)
235 def each_xml_element(node, name)
236 if node && node[name]
236 if node && node[name]
237 if node[name].is_a?(Hash)
237 if node[name].is_a?(Hash)
238 yield node[name]
238 yield node[name]
239 else
239 else
240 node[name].each do |element|
240 node[name].each do |element|
241 yield element
241 yield element
242 end
242 end
243 end
243 end
244 end
244 end
245 end
245 end
246
246
247 def target(path = '')
247 def target(path = '')
248 base = path.match(/^\//) ? root_url : url
248 base = path.match(/^\//) ? root_url : url
249 uri = "#{base}/#{path}"
249 uri = "#{base}/#{path}"
250 uri = URI.escape(URI.escape(uri), '[]')
250 uri = URI.escape(URI.escape(uri), '[]')
251 shell_quote(uri.gsub(/[?<>\*]/, ''))
251 shell_quote(uri.gsub(/[?<>\*]/, ''))
252 end
252 end
253 end
253 end
254 end
254 end
255 end
255 end
256 end
256 end
General Comments 0
You need to be logged in to leave comments. Login now