##// END OF EJS Templates
scm: change key name of configurable command name (#7517, #6159, #7047)....
Toshi MARUYAMA -
r4677:3ee203ef0c2b
parent child
Show More
@@ -1,125 +1,125
1 1 # = Redmine configuration file
2 2 #
3 3 # Each environment has it's own configuration options. If you are only
4 4 # running in production, only the production block needs to be configured.
5 5 # Environment specific configuration options override the default ones.
6 6 #
7 7 # Note that this file needs to be a valid YAML file.
8 8 #
9 9 # == Outgoing email settings (email_delivery setting)
10 10 #
11 11 # === Common configurations
12 12 #
13 13 # ==== Sendmail command
14 14 #
15 15 # production:
16 16 # email_delivery:
17 17 # delivery_method: :sendmail
18 18 #
19 19 # ==== Simple SMTP server at localhost
20 20 #
21 21 # production:
22 22 # email_delivery:
23 23 # delivery_method: :smtp
24 24 # smtp_settings:
25 25 # address: "localhost"
26 26 # port: 25
27 27 #
28 28 # ==== SMTP server at example.com using LOGIN authentication and checking HELO for foo.com
29 29 #
30 30 # production:
31 31 # email_delivery:
32 32 # delivery_method: :smtp
33 33 # smtp_settings:
34 34 # address: "example.com"
35 35 # port: 25
36 36 # authentication: :login
37 37 # domain: 'foo.com'
38 38 # user_name: 'myaccount'
39 39 # password: 'password'
40 40 #
41 41 # ==== SMTP server at example.com using PLAIN authentication
42 42 #
43 43 # production:
44 44 # email_delivery:
45 45 # delivery_method: :smtp
46 46 # smtp_settings:
47 47 # address: "example.com"
48 48 # port: 25
49 49 # authentication: :plain
50 50 # domain: 'example.com'
51 51 # user_name: 'myaccount'
52 52 # password: 'password'
53 53 #
54 54 # ==== SMTP server at using TLS (GMail)
55 55 #
56 56 # This requires some additional configuration. See the article at:
57 57 # http://redmineblog.com/articles/setup-redmine-to-send-email-using-gmail/
58 58 #
59 59 # production:
60 60 # email_delivery:
61 61 # delivery_method: :smtp
62 62 # smtp_settings:
63 63 # tls: true
64 64 # address: "smtp.gmail.com"
65 65 # port: 587
66 66 # domain: "smtp.gmail.com" # 'your.domain.com' for GoogleApps
67 67 # authentication: :plain
68 68 # user_name: "your_email@gmail.com"
69 69 # password: "your_password"
70 70 #
71 71 #
72 72 # === More configuration options
73 73 #
74 74 # See the "Configuration options" at the following website for a list of the
75 75 # full options allowed:
76 76 #
77 77 # http://wiki.rubyonrails.org/rails/pages/HowToSendEmailsWithActionMailer
78 78
79 79
80 80 # default configuration options for all environments
81 81 default:
82 82 # Outgoing emails configuration (see examples above)
83 83 email_delivery:
84 84 delivery_method: :smtp
85 85 smtp_settings:
86 86 address: smtp.example.net
87 87 port: 25
88 88 domain: example.net
89 89 authentication: :login
90 90 user_name: "redmine@example.net"
91 91 password: "redmine"
92 92
93 93 # Absolute path to the directory where attachments are stored.
94 94 # The default is the 'files' directory in your Redmine instance.
95 95 # Your Redmine instance needs to have write permission on this
96 96 # directory.
97 97 # Examples:
98 98 # attachments_storage_path: /var/redmine/files
99 99 # attachments_storage_path: D:/redmine/files
100 100 attachments_storage_path:
101 101
102 102 # Configuration of the autologin cookie.
103 103 # autologin_cookie_name: the name of the cookie (default: autologin)
104 104 # autologin_cookie_path: the cookie path (default: /)
105 105 # autologin_cookie_secure: true sets the cookie secure flag (default: false)
106 106 autologin_cookie_name:
107 107 autologin_cookie_path:
108 108 autologin_cookie_secure:
109 109
110 110 # Configuration of SCM executable command.
111 111 # Absolute path (e.g. /usr/local/bin/hg) or command name (e.g. hg.exe, bzr.exe)
112 112 # On Windows, *.cmd, *.bat (e.g. hg.cmd, bzr.bat) does not work.
113 scm_command_subversion: svn # (default: svn)
114 scm_command_mercurial: "\"C:\Program Files\TortoiseHg\hg.exe\"" # (default: hg)
115 scm_command_git: /usr/local/bin/git # (default: git)
116 scm_command_bazaar: bzr.exe # (default: bzr)
117 scm_command_darcs: darcs-1.0.9-i386-linux # (default: darcs)
113 scm_subversion_command: svn # (default: svn)
114 scm_mercurial_command: "\"C:\Program Files\TortoiseHg\hg.exe\"" # (default: hg)
115 scm_git_command: /usr/local/bin/git # (default: git)
116 scm_bazaar_command: bzr.exe # (default: bzr)
117 scm_darcs_command: darcs-1.0.9-i386-linux # (default: darcs)
118 118
119 119 # specific configuration options for production environment
120 120 # that overrides the default ones
121 121 production:
122 122
123 123 # specific configuration options for development environment
124 124 # that overrides the default ones
125 125 development:
@@ -1,189 +1,189
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/abstract_adapter'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class BazaarAdapter < AbstractAdapter
24 24
25 25 # Bazaar executable name
26 BZR_BIN = Redmine::Configuration['scm_command_bazaar'] || "bzr"
26 BZR_BIN = Redmine::Configuration['scm_bazaar_command'] || "bzr"
27 27
28 28 # Get info about the repository
29 29 def info
30 30 cmd = "#{BZR_BIN} revno #{target('')}"
31 31 info = nil
32 32 shellout(cmd) do |io|
33 33 if io.read =~ %r{^(\d+)\r?$}
34 34 info = Info.new({:root_url => url,
35 35 :lastrev => Revision.new({
36 36 :identifier => $1
37 37 })
38 38 })
39 39 end
40 40 end
41 41 return nil if $? && $?.exitstatus != 0
42 42 info
43 43 rescue CommandFailed
44 44 return nil
45 45 end
46 46
47 47 # Returns an Entries collection
48 48 # or nil if the given path doesn't exist in the repository
49 49 def entries(path=nil, identifier=nil)
50 50 path ||= ''
51 51 entries = Entries.new
52 52 cmd = "#{BZR_BIN} ls -v --show-ids"
53 53 identifier = -1 unless identifier && identifier.to_i > 0
54 54 cmd << " -r#{identifier.to_i}"
55 55 cmd << " #{target(path)}"
56 56 shellout(cmd) do |io|
57 57 prefix = "#{url}/#{path}".gsub('\\', '/')
58 58 logger.debug "PREFIX: #{prefix}"
59 59 re = %r{^V\s+(#{Regexp.escape(prefix)})?(\/?)([^\/]+)(\/?)\s+(\S+)\r?$}
60 60 io.each_line do |line|
61 61 next unless line =~ re
62 62 entries << Entry.new({:name => $3.strip,
63 63 :path => ((path.empty? ? "" : "#{path}/") + $3.strip),
64 64 :kind => ($4.blank? ? 'file' : 'dir'),
65 65 :size => nil,
66 66 :lastrev => Revision.new(:revision => $5.strip)
67 67 })
68 68 end
69 69 end
70 70 return nil if $? && $?.exitstatus != 0
71 71 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
72 72 entries.sort_by_name
73 73 end
74 74
75 75 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
76 76 path ||= ''
77 77 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : 'last:1'
78 78 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
79 79 revisions = Revisions.new
80 80 cmd = "#{BZR_BIN} log -v --show-ids -r#{identifier_to}..#{identifier_from} #{target(path)}"
81 81 shellout(cmd) do |io|
82 82 revision = nil
83 83 parsing = nil
84 84 io.each_line do |line|
85 85 if line =~ /^----/
86 86 revisions << revision if revision
87 87 revision = Revision.new(:paths => [], :message => '')
88 88 parsing = nil
89 89 else
90 90 next unless revision
91 91
92 92 if line =~ /^revno: (\d+)($|\s\[merge\]$)/
93 93 revision.identifier = $1.to_i
94 94 elsif line =~ /^committer: (.+)$/
95 95 revision.author = $1.strip
96 96 elsif line =~ /^revision-id:(.+)$/
97 97 revision.scmid = $1.strip
98 98 elsif line =~ /^timestamp: (.+)$/
99 99 revision.time = Time.parse($1).localtime
100 100 elsif line =~ /^ -----/
101 101 # partial revisions
102 102 parsing = nil unless parsing == 'message'
103 103 elsif line =~ /^(message|added|modified|removed|renamed):/
104 104 parsing = $1
105 105 elsif line =~ /^ (.*)$/
106 106 if parsing == 'message'
107 107 revision.message << "#{$1}\n"
108 108 else
109 109 if $1 =~ /^(.*)\s+(\S+)$/
110 110 path = $1.strip
111 111 revid = $2
112 112 case parsing
113 113 when 'added'
114 114 revision.paths << {:action => 'A', :path => "/#{path}", :revision => revid}
115 115 when 'modified'
116 116 revision.paths << {:action => 'M', :path => "/#{path}", :revision => revid}
117 117 when 'removed'
118 118 revision.paths << {:action => 'D', :path => "/#{path}", :revision => revid}
119 119 when 'renamed'
120 120 new_path = path.split('=>').last
121 121 revision.paths << {:action => 'M', :path => "/#{new_path.strip}", :revision => revid} if new_path
122 122 end
123 123 end
124 124 end
125 125 else
126 126 parsing = nil
127 127 end
128 128 end
129 129 end
130 130 revisions << revision if revision
131 131 end
132 132 return nil if $? && $?.exitstatus != 0
133 133 revisions
134 134 end
135 135
136 136 def diff(path, identifier_from, identifier_to=nil)
137 137 path ||= ''
138 138 if identifier_to
139 139 identifier_to = identifier_to.to_i
140 140 else
141 141 identifier_to = identifier_from.to_i - 1
142 142 end
143 143 if identifier_from
144 144 identifier_from = identifier_from.to_i
145 145 end
146 146 cmd = "#{BZR_BIN} diff -r#{identifier_to}..#{identifier_from} #{target(path)}"
147 147 diff = []
148 148 shellout(cmd) do |io|
149 149 io.each_line do |line|
150 150 diff << line
151 151 end
152 152 end
153 153 #return nil if $? && $?.exitstatus != 0
154 154 diff
155 155 end
156 156
157 157 def cat(path, identifier=nil)
158 158 cmd = "#{BZR_BIN} cat"
159 159 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
160 160 cmd << " #{target(path)}"
161 161 cat = nil
162 162 shellout(cmd) do |io|
163 163 io.binmode
164 164 cat = io.read
165 165 end
166 166 return nil if $? && $?.exitstatus != 0
167 167 cat
168 168 end
169 169
170 170 def annotate(path, identifier=nil)
171 171 cmd = "#{BZR_BIN} annotate --all"
172 172 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
173 173 cmd << " #{target(path)}"
174 174 blame = Annotate.new
175 175 shellout(cmd) do |io|
176 176 author = nil
177 177 identifier = nil
178 178 io.each_line do |line|
179 179 next unless line =~ %r{^(\d+) ([^|]+)\| (.*)$}
180 180 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
181 181 end
182 182 end
183 183 return nil if $? && $?.exitstatus != 0
184 184 blame
185 185 end
186 186 end
187 187 end
188 188 end
189 189 end
@@ -1,370 +1,370
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/abstract_adapter'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class CvsAdapter < AbstractAdapter
24 24
25 25 # CVS executable name
26 CVS_BIN = Redmine::Configuration['scm_command_cvs'] || "cvs"
26 CVS_BIN = Redmine::Configuration['scm_cvs_command'] || "cvs"
27 27
28 28 # Guidelines for the input:
29 29 # url -> the project-path, relative to the cvsroot (eg. module name)
30 30 # root_url -> the good old, sometimes damned, CVSROOT
31 31 # login -> unnecessary
32 32 # password -> unnecessary too
33 33 def initialize(url, root_url=nil, login=nil, password=nil)
34 34 @url = url
35 35 @login = login if login && !login.empty?
36 36 @password = (password || "") if @login
37 37 #TODO: better Exception here (IllegalArgumentException)
38 38 raise CommandFailed if root_url.blank?
39 39 @root_url = root_url
40 40 end
41 41
42 42 def root_url
43 43 @root_url
44 44 end
45 45
46 46 def url
47 47 @url
48 48 end
49 49
50 50 def info
51 51 logger.debug "<cvs> info"
52 52 Info.new({:root_url => @root_url, :lastrev => nil})
53 53 end
54 54
55 55 def get_previous_revision(revision)
56 56 CvsRevisionHelper.new(revision).prevRev
57 57 end
58 58
59 59 # Returns an Entries collection
60 60 # or nil if the given path doesn't exist in the repository
61 61 # this method is used by the repository-browser (aka LIST)
62 62 def entries(path=nil, identifier=nil)
63 63 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
64 64 path_with_project="#{url}#{with_leading_slash(path)}"
65 65 entries = Entries.new
66 66 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rls -e"
67 67 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
68 68 cmd << " #{shell_quote path_with_project}"
69 69 shellout(cmd) do |io|
70 70 io.each_line(){|line|
71 71 fields=line.chop.split('/',-1)
72 72 logger.debug(">>InspectLine #{fields.inspect}")
73 73
74 74 if fields[0]!="D"
75 75 entries << Entry.new({:name => fields[-5],
76 76 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
77 77 :path => "#{path}/#{fields[-5]}",
78 78 :kind => 'file',
79 79 :size => nil,
80 80 :lastrev => Revision.new({
81 81 :revision => fields[-4],
82 82 :name => fields[-4],
83 83 :time => Time.parse(fields[-3]),
84 84 :author => ''
85 85 })
86 86 })
87 87 else
88 88 entries << Entry.new({:name => fields[1],
89 89 :path => "#{path}/#{fields[1]}",
90 90 :kind => 'dir',
91 91 :size => nil,
92 92 :lastrev => nil
93 93 })
94 94 end
95 95 }
96 96 end
97 97 return nil if $? && $?.exitstatus != 0
98 98 entries.sort_by_name
99 99 end
100 100
101 101 STARTLOG="----------------------------"
102 102 ENDLOG ="============================================================================="
103 103
104 104 # Returns all revisions found between identifier_from and identifier_to
105 105 # in the repository. both identifier have to be dates or nil.
106 106 # these method returns nothing but yield every result in block
107 107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
108 108 logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
109 109
110 110 path_with_project="#{url}#{with_leading_slash(path)}"
111 111 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rlog"
112 112 cmd << " -d\">#{time_to_cvstime_rlog(identifier_from)}\"" if identifier_from
113 113 cmd << " #{shell_quote path_with_project}"
114 114 shellout(cmd) do |io|
115 115 state="entry_start"
116 116
117 117 commit_log=String.new
118 118 revision=nil
119 119 date=nil
120 120 author=nil
121 121 entry_path=nil
122 122 entry_name=nil
123 123 file_state=nil
124 124 branch_map=nil
125 125
126 126 io.each_line() do |line|
127 127
128 128 if state!="revision" && /^#{ENDLOG}/ =~ line
129 129 commit_log=String.new
130 130 revision=nil
131 131 state="entry_start"
132 132 end
133 133
134 134 if state=="entry_start"
135 135 branch_map=Hash.new
136 136 if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
137 137 entry_path = normalize_cvs_path($1)
138 138 entry_name = normalize_path(File.basename($1))
139 139 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
140 140 elsif /^head: (.+)$/ =~ line
141 141 entry_headRev = $1 #unless entry.nil?
142 142 elsif /^symbolic names:/ =~ line
143 143 state="symbolic" #unless entry.nil?
144 144 elsif /^#{STARTLOG}/ =~ line
145 145 commit_log=String.new
146 146 state="revision"
147 147 end
148 148 next
149 149 elsif state=="symbolic"
150 150 if /^(.*):\s(.*)/ =~ (line.strip)
151 151 branch_map[$1]=$2
152 152 else
153 153 state="tags"
154 154 next
155 155 end
156 156 elsif state=="tags"
157 157 if /^#{STARTLOG}/ =~ line
158 158 commit_log = ""
159 159 state="revision"
160 160 elsif /^#{ENDLOG}/ =~ line
161 161 state="head"
162 162 end
163 163 next
164 164 elsif state=="revision"
165 165 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
166 166 if revision
167 167
168 168 revHelper=CvsRevisionHelper.new(revision)
169 169 revBranch="HEAD"
170 170
171 171 branch_map.each() do |branch_name,branch_point|
172 172 if revHelper.is_in_branch_with_symbol(branch_point)
173 173 revBranch=branch_name
174 174 end
175 175 end
176 176
177 177 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
178 178
179 179 yield Revision.new({
180 180 :time => date,
181 181 :author => author,
182 182 :message=>commit_log.chomp,
183 183 :paths => [{
184 184 :revision => revision,
185 185 :branch=> revBranch,
186 186 :path=>entry_path,
187 187 :name=>entry_name,
188 188 :kind=>'file',
189 189 :action=>file_state
190 190 }]
191 191 })
192 192 end
193 193
194 194 commit_log=String.new
195 195 revision=nil
196 196
197 197 if /^#{ENDLOG}/ =~ line
198 198 state="entry_start"
199 199 end
200 200 next
201 201 end
202 202
203 203 if /^branches: (.+)$/ =~ line
204 204 #TODO: version.branch = $1
205 205 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
206 206 revision = $1
207 207 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
208 208 date = Time.parse($1)
209 209 author = /author: ([^;]+)/.match(line)[1]
210 210 file_state = /state: ([^;]+)/.match(line)[1]
211 211 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
212 212 # useful for stats or something else
213 213 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
214 214 # unless linechanges.nil?
215 215 # version.line_plus = linechanges[1]
216 216 # version.line_minus = linechanges[2]
217 217 # else
218 218 # version.line_plus = 0
219 219 # version.line_minus = 0
220 220 # end
221 221 else
222 222 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
223 223 end
224 224 end
225 225 end
226 226 end
227 227 end
228 228
229 229 def diff(path, identifier_from, identifier_to=nil)
230 230 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
231 231 path_with_project="#{url}#{with_leading_slash(path)}"
232 232 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
233 233 diff = []
234 234 shellout(cmd) do |io|
235 235 io.each_line do |line|
236 236 diff << line
237 237 end
238 238 end
239 239 return nil if $? && $?.exitstatus != 0
240 240 diff
241 241 end
242 242
243 243 def cat(path, identifier=nil)
244 244 identifier = (identifier) ? identifier : "HEAD"
245 245 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
246 246 path_with_project="#{url}#{with_leading_slash(path)}"
247 247 cmd = "#{CVS_BIN} -d #{shell_quote root_url} co"
248 248 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
249 249 cmd << " -p #{shell_quote path_with_project}"
250 250 cat = nil
251 251 shellout(cmd) do |io|
252 252 cat = io.read
253 253 end
254 254 return nil if $? && $?.exitstatus != 0
255 255 cat
256 256 end
257 257
258 258 def annotate(path, identifier=nil)
259 259 identifier = (identifier) ? identifier.to_i : "HEAD"
260 260 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
261 261 path_with_project="#{url}#{with_leading_slash(path)}"
262 262 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
263 263 blame = Annotate.new
264 264 shellout(cmd) do |io|
265 265 io.each_line do |line|
266 266 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
267 267 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
268 268 end
269 269 end
270 270 return nil if $? && $?.exitstatus != 0
271 271 blame
272 272 end
273 273
274 274 private
275 275
276 276 # Returns the root url without the connexion string
277 277 # :pserver:anonymous@foo.bar:/path => /path
278 278 # :ext:cvsservername:/path => /path
279 279 def root_url_path
280 280 root_url.to_s.gsub(/^:.+:\d*/, '')
281 281 end
282 282
283 283 # convert a date/time into the CVS-format
284 284 def time_to_cvstime(time)
285 285 return nil if time.nil?
286 286 return Time.now if time == 'HEAD'
287 287
288 288 unless time.kind_of? Time
289 289 time = Time.parse(time)
290 290 end
291 291 return time.strftime("%Y-%m-%d %H:%M:%S")
292 292 end
293 293
294 294 def time_to_cvstime_rlog(time)
295 295 return nil if time.nil?
296 296 t1 = time.clone.localtime
297 297 return t1.strftime("%Y-%m-%d %H:%M:%S")
298 298 end
299 299
300 300 def normalize_cvs_path(path)
301 301 normalize_path(path.gsub(/Attic\//,''))
302 302 end
303 303
304 304 def normalize_path(path)
305 305 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
306 306 end
307 307 end
308 308
309 309 class CvsRevisionHelper
310 310 attr_accessor :complete_rev, :revision, :base, :branchid
311 311
312 312 def initialize(complete_rev)
313 313 @complete_rev = complete_rev
314 314 parseRevision()
315 315 end
316 316
317 317 def branchPoint
318 318 return @base
319 319 end
320 320
321 321 def branchVersion
322 322 if isBranchRevision
323 323 return @base+"."+@branchid
324 324 end
325 325 return @base
326 326 end
327 327
328 328 def isBranchRevision
329 329 !@branchid.nil?
330 330 end
331 331
332 332 def prevRev
333 333 unless @revision==0
334 334 return buildRevision(@revision-1)
335 335 end
336 336 return buildRevision(@revision)
337 337 end
338 338
339 339 def is_in_branch_with_symbol(branch_symbol)
340 340 bpieces=branch_symbol.split(".")
341 341 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
342 342 return (branchVersion==branch_start)
343 343 end
344 344
345 345 private
346 346 def buildRevision(rev)
347 347 if rev== 0
348 348 @base
349 349 elsif @branchid.nil?
350 350 @base+"."+rev.to_s
351 351 else
352 352 @base+"."+@branchid+"."+rev.to_s
353 353 end
354 354 end
355 355
356 356 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
357 357 def parseRevision()
358 358 pieces=@complete_rev.split(".")
359 359 @revision=pieces.last.to_i
360 360 baseSize=1
361 361 baseSize+=(pieces.size/2)
362 362 @base=pieces[0..-baseSize].join(".")
363 363 if baseSize > 2
364 364 @branchid=pieces[-2]
365 365 end
366 366 end
367 367 end
368 368 end
369 369 end
370 370 end
@@ -1,225 +1,225
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/abstract_adapter'
19 19 require 'rexml/document'
20 20
21 21 module Redmine
22 22 module Scm
23 23 module Adapters
24 24 class DarcsAdapter < AbstractAdapter
25 25 # Darcs executable name
26 DARCS_BIN = Redmine::Configuration['scm_command_darcs'] || "darcs"
26 DARCS_BIN = Redmine::Configuration['scm_darcs_command'] || "darcs"
27 27
28 28 class << self
29 29 def client_version
30 30 @@client_version ||= (darcs_binary_version || [])
31 31 end
32 32
33 33 def darcs_binary_version
34 34 darcsversion = darcs_binary_version_from_command_line
35 35 if m = darcsversion.match(%r{\A(.*?)((\d+\.)+\d+)})
36 36 m[2].scan(%r{\d+}).collect(&:to_i)
37 37 end
38 38 end
39 39
40 40 def darcs_binary_version_from_command_line
41 41 shellout("#{DARCS_BIN} --version") { |io| io.read }.to_s
42 42 end
43 43 end
44 44
45 45 def initialize(url, root_url=nil, login=nil, password=nil)
46 46 @url = url
47 47 @root_url = url
48 48 end
49 49
50 50 def supports_cat?
51 51 # cat supported in darcs 2.0.0 and higher
52 52 self.class.client_version_above?([2, 0, 0])
53 53 end
54 54
55 55 # Get info about the darcs repository
56 56 def info
57 57 rev = revisions(nil,nil,nil,{:limit => 1})
58 58 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
59 59 end
60 60
61 61 # Returns an Entries collection
62 62 # or nil if the given path doesn't exist in the repository
63 63 def entries(path=nil, identifier=nil)
64 64 path_prefix = (path.blank? ? '' : "#{path}/")
65 65 if path.blank?
66 66 path = ( self.class.client_version_above?([2, 2, 0]) ? @url : '.' )
67 67 end
68 68 entries = Entries.new
69 69 cmd = "#{DARCS_BIN} annotate --repodir #{shell_quote @url} --xml-output"
70 70 cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
71 71 cmd << " #{shell_quote path}"
72 72 shellout(cmd) do |io|
73 73 begin
74 74 doc = REXML::Document.new(io)
75 75 if doc.root.name == 'directory'
76 76 doc.elements.each('directory/*') do |element|
77 77 next unless ['file', 'directory'].include? element.name
78 78 entries << entry_from_xml(element, path_prefix)
79 79 end
80 80 elsif doc.root.name == 'file'
81 81 entries << entry_from_xml(doc.root, path_prefix)
82 82 end
83 83 rescue
84 84 end
85 85 end
86 86 return nil if $? && $?.exitstatus != 0
87 87 entries.compact.sort_by_name
88 88 end
89 89
90 90 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
91 91 path = '.' if path.blank?
92 92 revisions = Revisions.new
93 93 cmd = "#{DARCS_BIN} changes --repodir #{shell_quote @url} --xml-output"
94 94 cmd << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from
95 95 cmd << " --last #{options[:limit].to_i}" if options[:limit]
96 96 shellout(cmd) do |io|
97 97 begin
98 98 doc = REXML::Document.new(io)
99 99 doc.elements.each("changelog/patch") do |patch|
100 100 message = patch.elements['name'].text
101 101 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
102 102 revisions << Revision.new({:identifier => nil,
103 103 :author => patch.attributes['author'],
104 104 :scmid => patch.attributes['hash'],
105 105 :time => Time.parse(patch.attributes['local_date']),
106 106 :message => message,
107 107 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
108 108 })
109 109 end
110 110 rescue
111 111 end
112 112 end
113 113 return nil if $? && $?.exitstatus != 0
114 114 revisions
115 115 end
116 116
117 117 def diff(path, identifier_from, identifier_to=nil)
118 118 path = '*' if path.blank?
119 119 cmd = "#{DARCS_BIN} diff --repodir #{shell_quote @url}"
120 120 if identifier_to.nil?
121 121 cmd << " --match #{shell_quote("hash #{identifier_from}")}"
122 122 else
123 123 cmd << " --to-match #{shell_quote("hash #{identifier_from}")}"
124 124 cmd << " --from-match #{shell_quote("hash #{identifier_to}")}"
125 125 end
126 126 cmd << " -u #{shell_quote path}"
127 127 diff = []
128 128 shellout(cmd) do |io|
129 129 io.each_line do |line|
130 130 diff << line
131 131 end
132 132 end
133 133 return nil if $? && $?.exitstatus != 0
134 134 diff
135 135 end
136 136
137 137 def cat(path, identifier=nil)
138 138 cmd = "#{DARCS_BIN} show content --repodir #{shell_quote @url}"
139 139 cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
140 140 cmd << " #{shell_quote path}"
141 141 cat = nil
142 142 shellout(cmd) do |io|
143 143 io.binmode
144 144 cat = io.read
145 145 end
146 146 return nil if $? && $?.exitstatus != 0
147 147 cat
148 148 end
149 149
150 150 private
151 151
152 152 # Returns an Entry from the given XML element
153 153 # or nil if the entry was deleted
154 154 def entry_from_xml(element, path_prefix)
155 155 modified_element = element.elements['modified']
156 156 if modified_element.elements['modified_how'].text.match(/removed/)
157 157 return nil
158 158 end
159 159
160 160 Entry.new({:name => element.attributes['name'],
161 161 :path => path_prefix + element.attributes['name'],
162 162 :kind => element.name == 'file' ? 'file' : 'dir',
163 163 :size => nil,
164 164 :lastrev => Revision.new({
165 165 :identifier => nil,
166 166 :scmid => modified_element.elements['patch'].attributes['hash']
167 167 })
168 168 })
169 169 end
170 170
171 171 def get_paths_for_patch(hash)
172 172 paths = get_paths_for_patch_raw(hash)
173 173 if self.class.client_version_above?([2, 4])
174 174 orig_paths = paths
175 175 paths = []
176 176 add_paths = []
177 177 add_paths_name = []
178 178 mod_paths = []
179 179 other_paths = []
180 180 orig_paths.each do |path|
181 181 if path[:action] == 'A'
182 182 add_paths << path
183 183 add_paths_name << path[:path]
184 184 elsif path[:action] == 'M'
185 185 mod_paths << path
186 186 else
187 187 other_paths << path
188 188 end
189 189 end
190 190 add_paths_name.each do |add_path|
191 191 mod_paths.delete_if { |m| m[:path] == add_path }
192 192 end
193 193 paths.concat add_paths
194 194 paths.concat mod_paths
195 195 paths.concat other_paths
196 196 end
197 197 paths
198 198 end
199 199
200 200 # Retrieve changed paths for a single patch
201 201 def get_paths_for_patch_raw(hash)
202 202 cmd = "#{DARCS_BIN} annotate --repodir #{shell_quote @url} --summary --xml-output"
203 203 cmd << " --match #{shell_quote("hash #{hash}")} "
204 204 paths = []
205 205 shellout(cmd) do |io|
206 206 begin
207 207 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
208 208 # A root element is added so that REXML doesn't raise an error
209 209 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
210 210 doc.elements.each('fake_root/summary/*') do |modif|
211 211 paths << {:action => modif.name[0,1].upcase,
212 212 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
213 213 }
214 214 end
215 215 rescue
216 216 end
217 217 end
218 218 paths
219 219 rescue CommandFailed
220 220 paths
221 221 end
222 222 end
223 223 end
224 224 end
225 225 end
@@ -1,277 +1,277
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/abstract_adapter'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class GitAdapter < AbstractAdapter
24 24 # Git executable name
25 GIT_BIN = Redmine::Configuration['scm_command_git'] || "git"
25 GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
26 26
27 27 def info
28 28 begin
29 29 Info.new(:root_url => url, :lastrev => lastrev('',nil))
30 30 rescue
31 31 nil
32 32 end
33 33 end
34 34
35 35 def branches
36 36 return @branches if @branches
37 37 @branches = []
38 38 cmd = "#{GIT_BIN} --git-dir #{target('')} branch --no-color"
39 39 shellout(cmd) do |io|
40 40 io.each_line do |line|
41 41 @branches << line.match('\s*\*?\s*(.*)$')[1]
42 42 end
43 43 end
44 44 @branches.sort!
45 45 end
46 46
47 47 def tags
48 48 return @tags if @tags
49 49 cmd = "#{GIT_BIN} --git-dir #{target('')} tag"
50 50 shellout(cmd) do |io|
51 51 @tags = io.readlines.sort!.map{|t| t.strip}
52 52 end
53 53 end
54 54
55 55 def default_branch
56 56 branches.include?('master') ? 'master' : branches.first
57 57 end
58 58
59 59 def entries(path=nil, identifier=nil)
60 60 path ||= ''
61 61 entries = Entries.new
62 62 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
63 63 cmd << shell_quote("HEAD:" + path) if identifier.nil?
64 64 cmd << shell_quote(identifier + ":" + path) if identifier
65 65 shellout(cmd) do |io|
66 66 io.each_line do |line|
67 67 e = line.chomp.to_s
68 68 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
69 69 type = $1
70 70 sha = $2
71 71 size = $3
72 72 name = $4
73 73 full_path = path.empty? ? name : "#{path}/#{name}"
74 74 entries << Entry.new({:name => name,
75 75 :path => full_path,
76 76 :kind => (type == "tree") ? 'dir' : 'file',
77 77 :size => (type == "tree") ? nil : size,
78 78 :lastrev => lastrev(full_path,identifier)
79 79 }) unless entries.detect{|entry| entry.name == name}
80 80 end
81 81 end
82 82 end
83 83 return nil if $? && $?.exitstatus != 0
84 84 entries.sort_by_name
85 85 end
86 86
87 87 def lastrev(path,rev)
88 88 return nil if path.nil?
89 89 cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --date=iso --pretty=fuller --no-merges -n 1 "
90 90 cmd << " #{shell_quote rev} " if rev
91 91 cmd << "-- #{shell_quote path} " unless path.empty?
92 92 lines = []
93 93 shellout(cmd) { |io| lines = io.readlines }
94 94 return nil if $? && $?.exitstatus != 0
95 95 begin
96 96 id = lines[0].split[1]
97 97 author = lines[1].match('Author:\s+(.*)$')[1]
98 98 time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1]).localtime
99 99
100 100 Revision.new({
101 101 :identifier => id,
102 102 :scmid => id,
103 103 :author => author,
104 104 :time => time,
105 105 :message => nil,
106 106 :paths => nil
107 107 })
108 108 rescue NoMethodError => e
109 109 logger.error("The revision '#{path}' has a wrong format")
110 110 return nil
111 111 end
112 112 end
113 113
114 114 def revisions(path, identifier_from, identifier_to, options={})
115 115 revisions = Revisions.new
116 116
117 117 cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --raw --date=iso --pretty=fuller "
118 118 cmd << " --reverse " if options[:reverse]
119 119 cmd << " --all " if options[:all]
120 120 cmd << " -n #{options[:limit].to_i} " if options[:limit]
121 121 cmd << "#{shell_quote(identifier_from + '..')}" if identifier_from
122 122 cmd << "#{shell_quote identifier_to}" if identifier_to
123 123 cmd << " --since=#{shell_quote(options[:since].strftime("%Y-%m-%d %H:%M:%S"))}" if options[:since]
124 124 cmd << " -- #{shell_quote path}" if path && !path.empty?
125 125
126 126 shellout(cmd) do |io|
127 127 files=[]
128 128 changeset = {}
129 129 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
130 130 revno = 1
131 131
132 132 io.each_line do |line|
133 133 if line =~ /^commit ([0-9a-f]{40})$/
134 134 key = "commit"
135 135 value = $1
136 136 if (parsing_descr == 1 || parsing_descr == 2)
137 137 parsing_descr = 0
138 138 revision = Revision.new({
139 139 :identifier => changeset[:commit],
140 140 :scmid => changeset[:commit],
141 141 :author => changeset[:author],
142 142 :time => Time.parse(changeset[:date]),
143 143 :message => changeset[:description],
144 144 :paths => files
145 145 })
146 146 if block_given?
147 147 yield revision
148 148 else
149 149 revisions << revision
150 150 end
151 151 changeset = {}
152 152 files = []
153 153 revno = revno + 1
154 154 end
155 155 changeset[:commit] = $1
156 156 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
157 157 key = $1
158 158 value = $2
159 159 if key == "Author"
160 160 changeset[:author] = value
161 161 elsif key == "CommitDate"
162 162 changeset[:date] = value
163 163 end
164 164 elsif (parsing_descr == 0) && line.chomp.to_s == ""
165 165 parsing_descr = 1
166 166 changeset[:description] = ""
167 167 elsif (parsing_descr == 1 || parsing_descr == 2) \
168 168 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/
169 169 parsing_descr = 2
170 170 fileaction = $1
171 171 filepath = $2
172 172 files << {:action => fileaction, :path => filepath}
173 173 elsif (parsing_descr == 1 || parsing_descr == 2) \
174 174 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/
175 175 parsing_descr = 2
176 176 fileaction = $1
177 177 filepath = $3
178 178 files << {:action => fileaction, :path => filepath}
179 179 elsif (parsing_descr == 1) && line.chomp.to_s == ""
180 180 parsing_descr = 2
181 181 elsif (parsing_descr == 1)
182 182 changeset[:description] << line[4..-1]
183 183 end
184 184 end
185 185
186 186 if changeset[:commit]
187 187 revision = Revision.new({
188 188 :identifier => changeset[:commit],
189 189 :scmid => changeset[:commit],
190 190 :author => changeset[:author],
191 191 :time => Time.parse(changeset[:date]),
192 192 :message => changeset[:description],
193 193 :paths => files
194 194 })
195 195
196 196 if block_given?
197 197 yield revision
198 198 else
199 199 revisions << revision
200 200 end
201 201 end
202 202 end
203 203
204 204 return nil if $? && $?.exitstatus != 0
205 205 revisions
206 206 end
207 207
208 208 def diff(path, identifier_from, identifier_to=nil)
209 209 path ||= ''
210 210
211 211 if identifier_to
212 212 cmd = "#{GIT_BIN} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}"
213 213 else
214 214 cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}"
215 215 end
216 216
217 217 cmd << " -- #{shell_quote path}" unless path.empty?
218 218 diff = []
219 219 shellout(cmd) do |io|
220 220 io.each_line do |line|
221 221 diff << line
222 222 end
223 223 end
224 224 return nil if $? && $?.exitstatus != 0
225 225 diff
226 226 end
227 227
228 228 def annotate(path, identifier=nil)
229 229 identifier = 'HEAD' if identifier.blank?
230 230 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}"
231 231 blame = Annotate.new
232 232 content = nil
233 233 shellout(cmd) { |io| io.binmode; content = io.read }
234 234 return nil if $? && $?.exitstatus != 0
235 235 # git annotates binary files
236 236 return nil if content.is_binary_data?
237 237 identifier = ''
238 238 # git shows commit author on the first occurrence only
239 239 authors_by_commit = {}
240 240 content.split("\n").each do |line|
241 241 if line =~ /^([0-9a-f]{39,40})\s.*/
242 242 identifier = $1
243 243 elsif line =~ /^author (.+)/
244 244 authors_by_commit[identifier] = $1.strip
245 245 elsif line =~ /^\t(.*)/
246 246 blame.add_line($1, Revision.new(:identifier => identifier, :author => authors_by_commit[identifier]))
247 247 identifier = ''
248 248 author = ''
249 249 end
250 250 end
251 251 blame
252 252 end
253 253
254 254 def cat(path, identifier=nil)
255 255 if identifier.nil?
256 256 identifier = 'HEAD'
257 257 end
258 258 cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}"
259 259 cat = nil
260 260 shellout(cmd) do |io|
261 261 io.binmode
262 262 cat = io.read
263 263 end
264 264 return nil if $? && $?.exitstatus != 0
265 265 cat
266 266 end
267 267
268 268 class Revision < Redmine::Scm::Adapters::Revision
269 269 # Returns the readable identifier
270 270 def format_identifier
271 271 identifier[0,8]
272 272 end
273 273 end
274 274 end
275 275 end
276 276 end
277 277 end
@@ -1,225 +1,225
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/abstract_adapter'
19 19 require 'cgi'
20 20
21 21 module Redmine
22 22 module Scm
23 23 module Adapters
24 24 class MercurialAdapter < AbstractAdapter
25 25
26 26 # Mercurial executable name
27 HG_BIN = Redmine::Configuration['scm_command_mercurial'] || "hg"
27 HG_BIN = Redmine::Configuration['scm_mercurial_command'] || "hg"
28 28 TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial"
29 29 TEMPLATE_NAME = "hg-template"
30 30 TEMPLATE_EXTENSION = "tmpl"
31 31
32 32 class << self
33 33 def client_version
34 34 @@client_version ||= (hgversion || [])
35 35 end
36 36
37 37 def hgversion
38 38 # The hg version is expressed either as a
39 39 # release number (eg 0.9.5 or 1.0) or as a revision
40 40 # id composed of 12 hexa characters.
41 41 theversion = hgversion_from_command_line
42 42 if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
43 43 m[2].scan(%r{\d+}).collect(&:to_i)
44 44 end
45 45 end
46 46
47 47 def hgversion_from_command_line
48 48 shellout("#{HG_BIN} --version") { |io| io.read }.to_s
49 49 end
50 50
51 51 def template_path
52 52 @@template_path ||= template_path_for(client_version)
53 53 end
54 54
55 55 def template_path_for(version)
56 56 if ((version <=> [0,9,5]) > 0) || version.empty?
57 57 ver = "1.0"
58 58 else
59 59 ver = "0.9.5"
60 60 end
61 61 "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
62 62 end
63 63 end
64 64
65 65 def info
66 66 cmd = "#{HG_BIN} -R #{target('')} root"
67 67 root_url = nil
68 68 shellout(cmd) do |io|
69 69 root_url = io.read
70 70 end
71 71 return nil if $? && $?.exitstatus != 0
72 72 info = Info.new({:root_url => root_url.chomp,
73 73 :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
74 74 })
75 75 info
76 76 rescue CommandFailed
77 77 return nil
78 78 end
79 79
80 80 def entries(path=nil, identifier=nil)
81 81 path ||= ''
82 82 entries = Entries.new
83 83 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
84 84 cmd << " -r #{hgrev(identifier)}"
85 85 cmd << " " + shell_quote("path:#{path}") unless path.empty?
86 86 shellout(cmd) do |io|
87 87 io.each_line do |line|
88 88 # HG uses antislashs as separator on Windows
89 89 line = line.gsub(/\\/, "/")
90 90 if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'')
91 91 e ||= line
92 92 e = e.chomp.split(%r{[\/\\]})
93 93 entries << Entry.new({:name => e.first,
94 94 :path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"),
95 95 :kind => (e.size > 1 ? 'dir' : 'file'),
96 96 :lastrev => Revision.new
97 97 }) unless e.empty? || entries.detect{|entry| entry.name == e.first}
98 98 end
99 99 end
100 100 end
101 101 return nil if $? && $?.exitstatus != 0
102 102 entries.sort_by_name
103 103 end
104 104
105 105 # Fetch the revisions by using a template file that
106 106 # makes Mercurial produce a xml output.
107 107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
108 108 revisions = Revisions.new
109 109 cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}"
110 110 if identifier_from && identifier_to
111 111 cmd << " -r #{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
112 112 elsif identifier_from
113 113 cmd << " -r #{hgrev(identifier_from)}:"
114 114 end
115 115 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
116 116 cmd << " #{shell_quote path}" unless path.blank?
117 117 shellout(cmd) do |io|
118 118 begin
119 119 # HG doesn't close the XML Document...
120 120 doc = REXML::Document.new(io.read << "</log>")
121 121 doc.elements.each("log/logentry") do |logentry|
122 122 paths = []
123 123 copies = logentry.get_elements('paths/path-copied')
124 124 logentry.elements.each("paths/path") do |path|
125 125 # Detect if the added file is a copy
126 126 if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text }
127 127 from_path = c.attributes['copyfrom-path']
128 128 from_rev = logentry.attributes['revision']
129 129 end
130 130 paths << {:action => path.attributes['action'],
131 131 :path => "/#{CGI.unescape(path.text)}",
132 132 :from_path => from_path ? "/#{CGI.unescape(from_path)}" : nil,
133 133 :from_revision => from_rev ? from_rev : nil
134 134 }
135 135 end
136 136 paths.sort! { |x,y| x[:path] <=> y[:path] }
137 137
138 138 revisions << Revision.new({:identifier => logentry.attributes['revision'],
139 139 :scmid => logentry.attributes['node'],
140 140 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
141 141 :time => Time.parse(logentry.elements['date'].text).localtime,
142 142 :message => logentry.elements['msg'].text,
143 143 :paths => paths
144 144 })
145 145 end
146 146 rescue
147 147 logger.debug($!)
148 148 end
149 149 end
150 150 return nil if $? && $?.exitstatus != 0
151 151 revisions
152 152 end
153 153
154 154 def diff(path, identifier_from, identifier_to=nil)
155 155 path ||= ''
156 156 diff_args = ''
157 157 diff = []
158 158 if identifier_to
159 159 diff_args = "-r #{hgrev(identifier_to)} -r #{hgrev(identifier_from)}"
160 160 else
161 161 if self.class.client_version_above?([1, 2])
162 162 diff_args = "-c #{hgrev(identifier_from)}"
163 163 else
164 164 return []
165 165 end
166 166 end
167 167 cmd = "#{HG_BIN} -R #{target('')} --config diff.git=false diff --nodates #{diff_args}"
168 168 cmd << " -I #{target(path)}" unless path.empty?
169 169 shellout(cmd) do |io|
170 170 io.each_line do |line|
171 171 diff << line
172 172 end
173 173 end
174 174 return nil if $? && $?.exitstatus != 0
175 175 diff
176 176 end
177 177
178 178 def cat(path, identifier=nil)
179 179 cmd = "#{HG_BIN} -R #{target('')} cat"
180 180 cmd << " -r #{hgrev(identifier)}"
181 181 cmd << " #{target(path)}"
182 182 cat = nil
183 183 shellout(cmd) do |io|
184 184 io.binmode
185 185 cat = io.read
186 186 end
187 187 return nil if $? && $?.exitstatus != 0
188 188 cat
189 189 end
190 190
191 191 def annotate(path, identifier=nil)
192 192 path ||= ''
193 193 cmd = "#{HG_BIN} -R #{target('')}"
194 194 cmd << " annotate -ncu"
195 195 cmd << " -r #{hgrev(identifier)}"
196 196 cmd << " #{target(path)}"
197 197 blame = Annotate.new
198 198 shellout(cmd) do |io|
199 199 io.each_line do |line|
200 200 next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$}
201 201 r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3,
202 202 :identifier => $3)
203 203 blame.add_line($4.rstrip, r)
204 204 end
205 205 end
206 206 return nil if $? && $?.exitstatus != 0
207 207 blame
208 208 end
209 209
210 210 class Revision < Redmine::Scm::Adapters::Revision
211 211 # Returns the readable identifier
212 212 def format_identifier
213 213 "#{revision}:#{scmid}"
214 214 end
215 215 end
216 216
217 217 # Returns correct revision identifier
218 218 def hgrev(identifier)
219 219 shell_quote(identifier.blank? ? 'tip' : identifier.to_s)
220 220 end
221 221 private :hgrev
222 222 end
223 223 end
224 224 end
225 225 end
@@ -1,256 +1,256
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/abstract_adapter'
19 19 require 'uri'
20 20
21 21 module Redmine
22 22 module Scm
23 23 module Adapters
24 24 class SubversionAdapter < AbstractAdapter
25 25
26 26 # SVN executable name
27 SVN_BIN = Redmine::Configuration['scm_command_subversion'] || "svn"
27 SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn"
28 28
29 29 class << self
30 30 def client_version
31 31 @@client_version ||= (svn_binary_version || [])
32 32 end
33 33
34 34 def svn_binary_version
35 35 cmd = "#{SVN_BIN} --version"
36 36 version = nil
37 37 shellout(cmd) do |io|
38 38 # Read svn version in first returned line
39 39 if m = io.read.to_s.match(%r{\A(.*?)((\d+\.)+\d+)})
40 40 version = m[2].scan(%r{\d+}).collect(&:to_i)
41 41 end
42 42 end
43 43 return nil if $? && $?.exitstatus != 0
44 44 version
45 45 end
46 46 end
47 47
48 48 # Get info about the svn repository
49 49 def info
50 50 cmd = "#{SVN_BIN} info --xml #{target}"
51 51 cmd << credentials_string
52 52 info = nil
53 53 shellout(cmd) do |io|
54 54 output = io.read
55 55 begin
56 56 doc = ActiveSupport::XmlMini.parse(output)
57 57 #root_url = doc.elements["info/entry/repository/root"].text
58 58 info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'],
59 59 :lastrev => Revision.new({
60 60 :identifier => doc['info']['entry']['commit']['revision'],
61 61 :time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime,
62 62 :author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "")
63 63 })
64 64 })
65 65 rescue
66 66 end
67 67 end
68 68 return nil if $? && $?.exitstatus != 0
69 69 info
70 70 rescue CommandFailed
71 71 return nil
72 72 end
73 73
74 74 # Returns an Entries collection
75 75 # or nil if the given path doesn't exist in the repository
76 76 def entries(path=nil, identifier=nil)
77 77 path ||= ''
78 78 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
79 79 entries = Entries.new
80 80 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
81 81 cmd << credentials_string
82 82 shellout(cmd) do |io|
83 83 output = io.read
84 84 begin
85 85 doc = ActiveSupport::XmlMini.parse(output)
86 86 each_xml_element(doc['lists']['list'], 'entry') do |entry|
87 87 commit = entry['commit']
88 88 commit_date = commit['date']
89 89 # Skip directory if there is no commit date (usually that
90 90 # means that we don't have read access to it)
91 91 next if entry['kind'] == 'dir' && commit_date.nil?
92 92 name = entry['name']['__content__']
93 93 entries << Entry.new({:name => URI.unescape(name),
94 94 :path => ((path.empty? ? "" : "#{path}/") + name),
95 95 :kind => entry['kind'],
96 96 :size => ((s = entry['size']) ? s['__content__'].to_i : nil),
97 97 :lastrev => Revision.new({
98 98 :identifier => commit['revision'],
99 99 :time => Time.parse(commit_date['__content__'].to_s).localtime,
100 100 :author => ((a = commit['author']) ? a['__content__'] : nil)
101 101 })
102 102 })
103 103 end
104 104 rescue Exception => e
105 105 logger.error("Error parsing svn output: #{e.message}")
106 106 logger.error("Output was:\n #{output}")
107 107 end
108 108 end
109 109 return nil if $? && $?.exitstatus != 0
110 110 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
111 111 entries.sort_by_name
112 112 end
113 113
114 114 def properties(path, identifier=nil)
115 115 # proplist xml output supported in svn 1.5.0 and higher
116 116 return nil unless self.class.client_version_above?([1, 5, 0])
117 117
118 118 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
119 119 cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}"
120 120 cmd << credentials_string
121 121 properties = {}
122 122 shellout(cmd) do |io|
123 123 output = io.read
124 124 begin
125 125 doc = ActiveSupport::XmlMini.parse(output)
126 126 each_xml_element(doc['properties']['target'], 'property') do |property|
127 127 properties[ property['name'] ] = property['__content__'].to_s
128 128 end
129 129 rescue
130 130 end
131 131 end
132 132 return nil if $? && $?.exitstatus != 0
133 133 properties
134 134 end
135 135
136 136 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
137 137 path ||= ''
138 138 identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
139 139 identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1
140 140 revisions = Revisions.new
141 141 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
142 142 cmd << credentials_string
143 143 cmd << " --verbose " if options[:with_paths]
144 144 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
145 145 cmd << ' ' + target(path)
146 146 shellout(cmd) do |io|
147 147 output = io.read
148 148 begin
149 149 doc = ActiveSupport::XmlMini.parse(output)
150 150 each_xml_element(doc['log'], 'logentry') do |logentry|
151 151 paths = []
152 152 each_xml_element(logentry['paths'], 'path') do |path|
153 153 paths << {:action => path['action'],
154 154 :path => path['__content__'],
155 155 :from_path => path['copyfrom-path'],
156 156 :from_revision => path['copyfrom-rev']
157 157 }
158 158 end if logentry['paths'] && logentry['paths']['path']
159 159 paths.sort! { |x,y| x[:path] <=> y[:path] }
160 160
161 161 revisions << Revision.new({:identifier => logentry['revision'],
162 162 :author => (logentry['author'] ? logentry['author']['__content__'] : ""),
163 163 :time => Time.parse(logentry['date']['__content__'].to_s).localtime,
164 164 :message => logentry['msg']['__content__'],
165 165 :paths => paths
166 166 })
167 167 end
168 168 rescue
169 169 end
170 170 end
171 171 return nil if $? && $?.exitstatus != 0
172 172 revisions
173 173 end
174 174
175 175 def diff(path, identifier_from, identifier_to=nil, type="inline")
176 176 path ||= ''
177 177 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
178 178 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
179 179
180 180 cmd = "#{SVN_BIN} diff -r "
181 181 cmd << "#{identifier_to}:"
182 182 cmd << "#{identifier_from}"
183 183 cmd << " #{target(path)}@#{identifier_from}"
184 184 cmd << credentials_string
185 185 diff = []
186 186 shellout(cmd) do |io|
187 187 io.each_line do |line|
188 188 diff << line
189 189 end
190 190 end
191 191 return nil if $? && $?.exitstatus != 0
192 192 diff
193 193 end
194 194
195 195 def cat(path, identifier=nil)
196 196 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
197 197 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
198 198 cmd << credentials_string
199 199 cat = nil
200 200 shellout(cmd) do |io|
201 201 io.binmode
202 202 cat = io.read
203 203 end
204 204 return nil if $? && $?.exitstatus != 0
205 205 cat
206 206 end
207 207
208 208 def annotate(path, identifier=nil)
209 209 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
210 210 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
211 211 cmd << credentials_string
212 212 blame = Annotate.new
213 213 shellout(cmd) do |io|
214 214 io.each_line do |line|
215 215 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
216 216 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
217 217 end
218 218 end
219 219 return nil if $? && $?.exitstatus != 0
220 220 blame
221 221 end
222 222
223 223 private
224 224
225 225 def credentials_string
226 226 str = ''
227 227 str << " --username #{shell_quote(@login)}" unless @login.blank?
228 228 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
229 229 str << " --no-auth-cache --non-interactive"
230 230 str
231 231 end
232 232
233 233 # Helper that iterates over the child elements of a xml node
234 234 # MiniXml returns a hash when a single child is found or an array of hashes for multiple children
235 235 def each_xml_element(node, name)
236 236 if node && node[name]
237 237 if node[name].is_a?(Hash)
238 238 yield node[name]
239 239 else
240 240 node[name].each do |element|
241 241 yield element
242 242 end
243 243 end
244 244 end
245 245 end
246 246
247 247 def target(path = '')
248 248 base = path.match(/^\//) ? root_url : url
249 249 uri = "#{base}/#{path}"
250 250 uri = URI.escape(URI.escape(uri), '[]')
251 251 shell_quote(uri.gsub(/[?<>\*]/, ''))
252 252 end
253 253 end
254 254 end
255 255 end
256 256 end
General Comments 0
You need to be logged in to leave comments. Login now