# Absolute path (e.g. /usr/local/bin/hg) or command name (e.g. hg.exe, bzr.exe)
# On Windows, *.cmd, *.bat (e.g. hg.cmd, bzr.bat) does not work.
scm_subversion_command: svn # (default: svn)
- scm_mercurial_command: "\"C:\Program Files\TortoiseHg\hg.exe\"" # (default: hg)
+ scm_mercurial_command: C:\Program Files\TortoiseHg\hg.exe # (default: hg)
scm_git_command: /usr/local/bin/git # (default: git)
scm_cvs_command: cvs # (default: cvs)
scm_bazaar_command: bzr.exe # (default: bzr)
class AbstractAdapter #:nodoc:
class << self
+ def client_command
+ ""
+ end
+
# Returns the version of the scm client
# Eg: [1, 5, 0] or [] if unknown
def client_version
def client_version_above?(v, options={})
((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
end
+
+ def client_available
+ true
+ end
+
+ def shell_quote(str)
+ if Redmine::Platform.mswin?
+ '"' + str.gsub(/"/, '\\"') + '"'
+ else
+ "'" + str.gsub(/'/, "'\"'\"'") + "'"
+ end
+ end
end
-
+
def initialize(url, root_url=nil, login=nil, password=nil)
@url = url
@login = login if login && !login.empty?
path ||= ''
(path[-1,1] == "/") ? path : "#{path}/"
end
-
+
def without_leading_slash(path)
path ||= ''
path.gsub(%r{^/+}, '')
path ||= ''
(path[-1,1] == "/") ? path[0..-2] : path
end
-
+
def shell_quote(str)
- if Redmine::Platform.mswin?
- '"' + str.gsub(/"/, '\\"') + '"'
- else
- "'" + str.gsub(/'/, "'\"'\"'") + "'"
- end
+ self.class.shell_quote(str)
end
private
base = path.match(/^\//) ? root_url : url
shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
end
-
+
def logger
self.class.logger
end
-
+
def shellout(cmd, &block)
self.class.shellout(cmd, &block)
end
module Redmine
module Scm
- module Adapters
+ module Adapters
class BazaarAdapter < AbstractAdapter
-
+
# Bazaar executable name
BZR_BIN = Redmine::Configuration['scm_bazaar_command'] || "bzr"
-
+
+ class << self
+ def client_command
+ @@bin ||= BZR_BIN
+ end
+
+ def sq_bin
+ @@sq_bin ||= shell_quote(BZR_BIN)
+ end
+ end
+
# Get info about the repository
def info
- cmd = "#{BZR_BIN} revno #{target('')}"
+ cmd = "#{self.class.sq_bin} revno #{target('')}"
info = nil
shellout(cmd) do |io|
if io.read =~ %r{^(\d+)\r?$}
rescue CommandFailed
return nil
end
-
+
# Returns an Entries collection
# or nil if the given path doesn't exist in the repository
def entries(path=nil, identifier=nil)
path ||= ''
entries = Entries.new
- cmd = "#{BZR_BIN} ls -v --show-ids"
+ cmd = "#{self.class.sq_bin} ls -v --show-ids"
identifier = -1 unless identifier && identifier.to_i > 0
cmd << " -r#{identifier.to_i}"
cmd << " #{target(path)}"
logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
entries.sort_by_name
end
-
+
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path ||= ''
identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : 'last:1'
identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
revisions = Revisions.new
- cmd = "#{BZR_BIN} log -v --show-ids -r#{identifier_to}..#{identifier_from} #{target(path)}"
+ cmd = "#{self.class.sq_bin} log -v --show-ids -r#{identifier_to}..#{identifier_from} #{target(path)}"
shellout(cmd) do |io|
revision = nil
parsing = nil
return nil if $? && $?.exitstatus != 0
revisions
end
-
+
def diff(path, identifier_from, identifier_to=nil)
path ||= ''
if identifier_to
if identifier_from
identifier_from = identifier_from.to_i
end
- cmd = "#{BZR_BIN} diff -r#{identifier_to}..#{identifier_from} #{target(path)}"
+ cmd = "#{self.class.sq_bin} diff -r#{identifier_to}..#{identifier_from} #{target(path)}"
diff = []
shellout(cmd) do |io|
io.each_line do |line|
#return nil if $? && $?.exitstatus != 0
diff
end
-
+
def cat(path, identifier=nil)
- cmd = "#{BZR_BIN} cat"
+ cmd = "#{self.class.sq_bin} cat"
cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
cmd << " #{target(path)}"
cat = nil
return nil if $? && $?.exitstatus != 0
cat
end
-
+
def annotate(path, identifier=nil)
- cmd = "#{BZR_BIN} annotate --all"
+ cmd = "#{self.class.sq_bin} annotate --all"
cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
cmd << " #{target(path)}"
blame = Annotate.new
# CVS executable name
CVS_BIN = Redmine::Configuration['scm_cvs_command'] || "cvs"
-
+
+ class << self
+ def client_command
+ @@bin ||= CVS_BIN
+ end
+
+ def sq_bin
+ @@sq_bin ||= shell_quote(CVS_BIN)
+ end
+ end
+
# Guidelines for the input:
# url -> the project-path, relative to the cvsroot (eg. module name)
# root_url -> the good old, sometimes damned, CVSROOT
raise CommandFailed if root_url.blank?
@root_url = root_url
end
-
+
def root_url
@root_url
end
-
+
def url
@url
end
-
+
def info
logger.debug "<cvs> info"
Info.new({:root_url => @root_url, :lastrev => nil})
end
-
+
def get_previous_revision(revision)
CvsRevisionHelper.new(revision).prevRev
end
logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
path_with_project="#{url}#{with_leading_slash(path)}"
entries = Entries.new
- cmd = "#{CVS_BIN} -d #{shell_quote root_url} rls -e"
+ cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rls -e"
cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
cmd << " #{shell_quote path_with_project}"
shellout(cmd) do |io|
logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
path_with_project="#{url}#{with_leading_slash(path)}"
- cmd = "#{CVS_BIN} -d #{shell_quote root_url} rlog"
+ cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rlog"
cmd << " -d\">#{time_to_cvstime_rlog(identifier_from)}\"" if identifier_from
cmd << " #{shell_quote path_with_project}"
shellout(cmd) do |io|
def diff(path, identifier_from, identifier_to=nil)
logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
path_with_project="#{url}#{with_leading_slash(path)}"
- cmd = "#{CVS_BIN} -d #{shell_quote root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
+ cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
diff = []
shellout(cmd) do |io|
io.each_line do |line|
identifier = (identifier) ? identifier : "HEAD"
logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
path_with_project="#{url}#{with_leading_slash(path)}"
- cmd = "#{CVS_BIN} -d #{shell_quote root_url} co"
+ cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} co"
cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
cmd << " -p #{shell_quote path_with_project}"
cat = nil
identifier = (identifier) ? identifier.to_i : "HEAD"
logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
path_with_project="#{url}#{with_leading_slash(path)}"
- cmd = "#{CVS_BIN} -d #{shell_quote root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
+ cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
blame = Annotate.new
shellout(cmd) do |io|
io.each_line do |line|
module Redmine
module Scm
- module Adapters
+ module Adapters
class DarcsAdapter < AbstractAdapter
# Darcs executable name
DARCS_BIN = Redmine::Configuration['scm_darcs_command'] || "darcs"
-
+
class << self
+ def client_command
+ @@bin ||= DARCS_BIN
+ end
+
+ def sq_bin
+ @@sq_bin ||= shell_quote(DARCS_BIN)
+ end
+
def client_version
@@client_version ||= (darcs_binary_version || [])
end
-
+
def darcs_binary_version
darcsversion = darcs_binary_version_from_command_line
if m = darcsversion.match(%r{\A(.*?)((\d+\.)+\d+)})
end
def darcs_binary_version_from_command_line
- shellout("#{DARCS_BIN} --version") { |io| io.read }.to_s
+ shellout("#{sq_bin} --version") { |io| io.read }.to_s
end
end
rev = revisions(nil,nil,nil,{:limit => 1})
rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
end
-
+
# Returns an Entries collection
# or nil if the given path doesn't exist in the repository
def entries(path=nil, identifier=nil)
path = ( self.class.client_version_above?([2, 2, 0]) ? @url : '.' )
end
entries = Entries.new
- cmd = "#{DARCS_BIN} annotate --repodir #{shell_quote @url} --xml-output"
+ cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --xml-output"
cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
cmd << " #{shell_quote path}"
shellout(cmd) do |io|
return nil if $? && $?.exitstatus != 0
entries.compact.sort_by_name
end
-
+
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path = '.' if path.blank?
revisions = Revisions.new
- cmd = "#{DARCS_BIN} changes --repodir #{shell_quote @url} --xml-output"
+ cmd = "#{self.class.sq_bin} changes --repodir #{shell_quote @url} --xml-output"
cmd << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from
cmd << " --last #{options[:limit].to_i}" if options[:limit]
shellout(cmd) do |io|
return nil if $? && $?.exitstatus != 0
revisions
end
-
+
def diff(path, identifier_from, identifier_to=nil)
path = '*' if path.blank?
- cmd = "#{DARCS_BIN} diff --repodir #{shell_quote @url}"
+ cmd = "#{self.class.sq_bin} diff --repodir #{shell_quote @url}"
if identifier_to.nil?
cmd << " --match #{shell_quote("hash #{identifier_from}")}"
else
return nil if $? && $?.exitstatus != 0
diff
end
-
+
def cat(path, identifier=nil)
- cmd = "#{DARCS_BIN} show content --repodir #{shell_quote @url}"
+ cmd = "#{self.class.sq_bin} show content --repodir #{shell_quote @url}"
cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
cmd << " #{shell_quote path}"
cat = nil
end
private
-
+
# Returns an Entry from the given XML element
# or nil if the entry was deleted
def entry_from_xml(element, path_prefix)
end
paths
end
-
+
# Retrieve changed paths for a single patch
def get_paths_for_patch_raw(hash)
- cmd = "#{DARCS_BIN} annotate --repodir #{shell_quote @url} --summary --xml-output"
+ cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --summary --xml-output"
cmd << " --match #{shell_quote("hash #{hash}")} "
paths = []
shellout(cmd) do |io|
module Scm
module Adapters
class FilesystemAdapter < AbstractAdapter
-
+
+ class << self
+ def client_available
+ true
+ end
+ end
def initialize(url, root_url=nil, login=nil, password=nil)
@url = with_trailling_slash(url)
module Redmine
module Scm
- module Adapters
+ module Adapters
class GitAdapter < AbstractAdapter
# Git executable name
GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
+ class << self
+ def client_command
+ @@bin ||= GIT_BIN
+ end
+
+ def sq_bin
+ @@sq_bin ||= shell_quote(GIT_BIN)
+ end
+
+ def client_available
+ !client_version.empty?
+ end
+ end
+
def info
begin
Info.new(:root_url => url, :lastrev => lastrev('',nil))
def branches
return @branches if @branches
@branches = []
- cmd = "#{GIT_BIN} --git-dir #{target('')} branch --no-color"
+ cmd = "#{self.class.sq_bin} --git-dir #{target('')} branch --no-color"
shellout(cmd) do |io|
io.each_line do |line|
@branches << line.match('\s*\*?\s*(.*)$')[1]
def tags
return @tags if @tags
- cmd = "#{GIT_BIN} --git-dir #{target('')} tag"
+ cmd = "#{self.class.sq_bin} --git-dir #{target('')} tag"
shellout(cmd) do |io|
@tags = io.readlines.sort!.map{|t| t.strip}
end
def entries(path=nil, identifier=nil)
path ||= ''
entries = Entries.new
- cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
+ cmd = "#{self.class.sq_bin} --git-dir #{target('')} ls-tree -l "
cmd << shell_quote("HEAD:" + path) if identifier.nil?
cmd << shell_quote(identifier + ":" + path) if identifier
shellout(cmd) do |io|
def lastrev(path,rev)
return nil if path.nil?
- cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --date=iso --pretty=fuller --no-merges -n 1 "
+ cmd = "#{self.class.sq_bin} --git-dir #{target('')} log --no-color --date=iso --pretty=fuller --no-merges -n 1 "
cmd << " #{shell_quote rev} " if rev
cmd << "-- #{shell_quote path} " unless path.empty?
lines = []
def revisions(path, identifier_from, identifier_to, options={})
revisions = Revisions.new
- cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --raw --date=iso --pretty=fuller "
+ cmd = "#{self.class.sq_bin} --git-dir #{target('')} log --no-color --raw --date=iso --pretty=fuller "
cmd << " --reverse " if options[:reverse]
cmd << " --all " if options[:all]
cmd << " -n #{options[:limit].to_i} " if options[:limit]
path ||= ''
if identifier_to
- cmd = "#{GIT_BIN} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}"
+ cmd = "#{self.class.sq_bin} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}"
else
- cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}"
+ cmd = "#{self.class.sq_bin} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}"
end
cmd << " -- #{shell_quote path}" unless path.empty?
def annotate(path, identifier=nil)
identifier = 'HEAD' if identifier.blank?
- cmd = "#{GIT_BIN} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}"
+ cmd = "#{self.class.sq_bin} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}"
blame = Annotate.new
content = nil
shellout(cmd) { |io| io.binmode; content = io.read }
if identifier.nil?
identifier = 'HEAD'
end
- cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}"
+ cmd = "#{self.class.sq_bin} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}"
cat = nil
shellout(cmd) do |io|
io.binmode
module Redmine
module Scm
- module Adapters
+ module Adapters
class MercurialAdapter < AbstractAdapter
# Mercurial executable name
TEMPLATE_EXTENSION = "tmpl"
class << self
+ def client_command
+ @@bin ||= HG_BIN
+ end
+
+ def sq_bin
+ @@sq_bin ||= shell_quote(HG_BIN)
+ end
+
def client_version
@@client_version ||= (hgversion || [])
end
- def hgversion
+ def client_available
+ !client_version.empty?
+ end
+
+ def hgversion
# The hg version is expressed either as a
# release number (eg 0.9.5 or 1.0) or as a revision
# id composed of 12 hexa characters.
end
def hgversion_from_command_line
- shellout("#{HG_BIN} --version") { |io| io.read }.to_s
+ shellout("#{sq_bin} --version") { |io| io.read }.to_s
end
def template_path
end
def info
- cmd = "#{HG_BIN} -R #{target('')} root"
+ cmd = "#{self.class.sq_bin} -R #{target('')} root"
root_url = nil
shellout(cmd) do |io|
root_url = io.read
def entries(path=nil, identifier=nil)
path ||= ''
entries = Entries.new
- cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
+ cmd = "#{self.class.sq_bin} -R #{target('')} --cwd #{target('')} locate"
cmd << " -r #{hgrev(identifier)}"
cmd << " " + shell_quote("path:#{path}") unless path.empty?
shellout(cmd) do |io|
# makes Mercurial produce a xml output.
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
revisions = Revisions.new
- cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}"
+ cmd = "#{self.class.sq_bin} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}"
if identifier_from && identifier_to
cmd << " -r #{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
elsif identifier_from
return []
end
end
- cmd = "#{HG_BIN} -R #{target('')} --config diff.git=false diff --nodates #{diff_args}"
+ cmd = "#{self.class.sq_bin} -R #{target('')} --config diff.git=false diff --nodates #{diff_args}"
cmd << " -I #{target(path)}" unless path.empty?
shellout(cmd) do |io|
io.each_line do |line|
end
def cat(path, identifier=nil)
- cmd = "#{HG_BIN} -R #{target('')} cat"
+ cmd = "#{self.class.sq_bin} -R #{target('')} cat"
cmd << " -r #{hgrev(identifier)}"
cmd << " #{target(path)}"
cat = nil
def annotate(path, identifier=nil)
path ||= ''
- cmd = "#{HG_BIN} -R #{target('')}"
+ cmd = "#{self.class.sq_bin} -R #{target('')}"
cmd << " annotate -ncu"
cmd << " -r #{hgrev(identifier)}"
cmd << " #{target(path)}"
\r
module Redmine\r
module Scm\r
- module Adapters \r
+ module Adapters\r
class SubversionAdapter < AbstractAdapter\r
- \r
+\r
# SVN executable name\r
SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn"\r
- \r
+\r
class << self\r
+ def client_command\r
+ @@bin ||= SVN_BIN\r
+ end\r
+\r
+ def sq_bin\r
+ @@sq_bin ||= shell_quote(SVN_BIN)\r
+ end\r
+\r
def client_version\r
@@client_version ||= (svn_binary_version || [])\r
end\r
- \r
+\r
def svn_binary_version\r
- cmd = "#{SVN_BIN} --version"\r
+ cmd = "#{sq_bin} --version"\r
version = nil\r
shellout(cmd) do |io|\r
# Read svn version in first returned line\r
version\r
end\r
end\r
- \r
+\r
# Get info about the svn repository\r
def info\r
- cmd = "#{SVN_BIN} info --xml #{target}"\r
+ cmd = "#{self.class.sq_bin} info --xml #{target}"\r
cmd << credentials_string\r
info = nil\r
shellout(cmd) do |io|\r
rescue CommandFailed\r
return nil\r
end\r
- \r
+\r
# Returns an Entries collection\r
# or nil if the given path doesn't exist in the repository\r
def entries(path=nil, identifier=nil)\r
path ||= ''\r
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"\r
entries = Entries.new\r
- cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"\r
+ cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}"\r
cmd << credentials_string\r
shellout(cmd) do |io|\r
output = io.read\r
logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?\r
entries.sort_by_name\r
end\r
- \r
+\r
def properties(path, identifier=nil)\r
# proplist xml output supported in svn 1.5.0 and higher\r
return nil unless self.class.client_version_above?([1, 5, 0])\r
\r
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"\r
- cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}"\r
+ cmd = "#{self.class.sq_bin} proplist --verbose --xml #{target(path)}@#{identifier}"\r
cmd << credentials_string\r
properties = {}\r
shellout(cmd) do |io|\r
return nil if $? && $?.exitstatus != 0\r
properties\r
end\r
- \r
+\r
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})\r
path ||= ''\r
identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"\r
identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1\r
revisions = Revisions.new\r
- cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"\r
+ cmd = "#{self.class.sq_bin} log --xml -r #{identifier_from}:#{identifier_to}"\r
cmd << credentials_string\r
cmd << " --verbose " if options[:with_paths]\r
cmd << " --limit #{options[:limit].to_i}" if options[:limit]\r
return nil if $? && $?.exitstatus != 0\r
revisions\r
end\r
- \r
+\r
def diff(path, identifier_from, identifier_to=nil, type="inline")\r
path ||= ''\r
identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''\r
identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)\r
- \r
- cmd = "#{SVN_BIN} diff -r "\r
+\r
+ cmd = "#{self.class.sq_bin} diff -r "\r
cmd << "#{identifier_to}:"\r
cmd << "#{identifier_from}"\r
cmd << " #{target(path)}@#{identifier_from}"\r
return nil if $? && $?.exitstatus != 0\r
diff\r
end\r
- \r
+\r
def cat(path, identifier=nil)\r
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"\r
- cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"\r
+ cmd = "#{self.class.sq_bin} cat #{target(path)}@#{identifier}"\r
cmd << credentials_string\r
cat = nil\r
shellout(cmd) do |io|\r
return nil if $? && $?.exitstatus != 0\r
cat\r
end\r
- \r
+\r
def annotate(path, identifier=nil)\r
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"\r
- cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"\r
+ cmd = "#{self.class.sq_bin} blame #{target(path)}@#{identifier}"\r
cmd << credentials_string\r
blame = Annotate.new\r
shellout(cmd) do |io|\r