Commit dd596d6d authored by Enrique García Cota's avatar Enrique García Cota

first release with git and svn

parent 59f92c97
......@@ -143,6 +143,10 @@ module Redmine
return nil
end
def save_entry_to_temp_file(path, identifier)
return nil
end
def with_leading_slash(path)
path ||= ''
(path[0,1]!="/") ? "/#{path}" : path
......@@ -183,34 +187,39 @@ module Redmine
self.class.logger
end
def shellout(cmd, &block)
self.class.shellout(cmd, &block)
def shellout(cmd, output_path=nil, &block)
self.class.shellout(cmd, output_path, &block)
end
def self.logger
RAILS_DEFAULT_LOGGER
end
def self.shellout(cmd, &block)
logger.debug "Shelling out: #{strip_credential(cmd)}" if logger && logger.debug?
if Rails.env == 'development'
# Capture stderr when running in dev environment
cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log"
end
def self.process_cmd(cmd, output_path)
cmd = Rails.env == 'development' ? "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log" : cmd
cmd = "#{cmd} >> #{output_path}" if output_path.present?
cmd
end
def self.get_reading_mode_for_ruby_version
RUBY_VERSION < '1.9' ? 'r+' : 'r+:ASCII-8BIT'
end
def self.shellout(cmd, output_path=nil, &block)
logger.debug("Shelling out: #{strip_credential(cmd)}") if logger && logger.respond_to?(:debug)
cmd = process_cmd(cmd, output_path)
mode = get_reading_mode_for_ruby_version
begin
if RUBY_VERSION < '1.9'
mode = "r+"
else
mode = "r+:ASCII-8BIT"
end
result = nil
IO.popen(cmd, mode) do |io|
io.close_write
block.call(io) if block_given?
result = block.call(io) if block_given?
end
result
rescue Errno::ENOENT => e
msg = strip_credential(e.message)
# The command failed, log it and re-raise
logger.error("SCM command failed, make sure that your SCM binary (eg. svn) is in PATH (#{ENV['PATH']}): #{strip_credential(cmd)}\n with: #{msg}")
cmd = strip_credential(cmd)
logger.error("SCM command failed, make sure that your SCM binary (eg. svn) is in PATH (#{ENV['PATH']}): #{cmd}\n with: #{msg}")
raise CommandFailed.new(msg)
end
end
......
......@@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'redmine/scm/adapters/abstract_adapter'
require_dependency 'redmine/scm/adapters/abstract_adapter'
module Redmine
module Scm
......
......@@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'redmine/scm/adapters/abstract_adapter'
require_dependency 'redmine/scm/adapters/abstract_adapter'
module Redmine
module Scm
......
......@@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'redmine/scm/adapters/abstract_adapter'
require_dependency 'redmine/scm/adapters/abstract_adapter'
require 'rexml/document'
module Redmine
......
......@@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'redmine/scm/adapters/abstract_adapter'
require_dependency 'redmine/scm/adapters/abstract_adapter'
require 'find'
module Redmine
......
......@@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'redmine/scm/adapters/abstract_adapter'
require_dependency 'redmine/scm/adapters/abstract_adapter'
module Redmine
module Scm
......@@ -24,9 +24,6 @@ module Redmine
# Git executable name
GIT_BIN = Redmine::Configuration['scm_git_command'] || "git" unless defined?(GIT_BIN)
# raised if scm command exited with error, e.g. unknown revision.
class ScmCommandAborted < CommandFailed; end
class << self
def client_command
@@bin ||= GIT_BIN
......@@ -75,26 +72,22 @@ module Redmine
def branches
return @branches if @branches
@branches = []
cmd_args = %w|branch --no-color|
scm_cmd(*cmd_args) do |io|
scm_cmd(cmd_args) do |io|
@branches = []
io.each_line do |line|
@branches << line.match('\s*\*?\s*(.*)$')[1]
end
@branches.sort!
end
@branches.sort!
rescue ScmCommandAborted
nil
end
def tags
return @tags if @tags
cmd_args = %w|tag|
scm_cmd(*cmd_args) do |io|
scm_cmd(cmd_args) do |io|
@tags = io.readlines.sort!.map{|t| t.strip}
end
rescue ScmCommandAborted
nil
end
def default_branch
......@@ -106,11 +99,14 @@ module Redmine
def entries(path=nil, identifier=nil)
path ||= ''
p = scm_iconv(@path_encoding, 'UTF-8', path)
entries = Entries.new
cmd_args = %w|ls-tree -l|
cmd_args << "HEAD:#{p}" if identifier.nil?
cmd_args << "#{identifier}:#{p}" if identifier
scm_cmd(*cmd_args) do |io|
scm_cmd(cmd_args) do |io|
entries = Entries.new
io.each_line do |line|
e = line.chomp.to_s
if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
......@@ -124,18 +120,20 @@ module Redmine
full_path = p.empty? ? name : "#{p}/#{name}"
n = scm_iconv('UTF-8', @path_encoding, name)
full_p = scm_iconv('UTF-8', @path_encoding, full_path)
entries << Entry.new({:name => n,
:path => full_p,
:kind => (type == "tree") ? 'dir' : 'file',
:size => (type == "tree") ? nil : size,
:lastrev => @flag_report_last_commit ? lastrev(full_path, identifier) : Revision.new
}) unless entries.detect{|entry| entry.name == name}
unless entries.detect{|entry| entry.name == name}
entries << Entry.new({:name => n,
:path => full_p,
:kind => (type == "tree") ? 'dir' : 'file',
:size => (type == "tree") ? nil : size,
:lastrev => @flag_report_last_commit ? lastrev(full_path, identifier) : Revision.new
})
end
end
end
entries.sort_by_name
end
entries.sort_by_name
rescue ScmCommandAborted
nil
end
def lastrev(path, rev)
......@@ -143,9 +141,9 @@ module Redmine
cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1|
cmd_args << rev if rev
cmd_args << "--" << path unless path.empty?
lines = []
scm_cmd(*cmd_args) { |io| lines = io.readlines }
begin
lines = scm_cmd(cmd_args) { |io| io.readlines }
if lines
begin
id = lines[0].split[1]
author = lines[1].match('Author:\s+(.*)$')[1]
time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1])
......@@ -157,17 +155,17 @@ module Redmine
:time => time,
:message => nil,
:paths => nil
})
rescue NoMethodError => e
})
rescue NoMethodError => e
logger.error("The revision '#{path}' has a wrong format")
return nil
end
end
rescue ScmCommandAborted
nil
end
def revisions(path, identifier_from, identifier_to, options={})
revisions = Revisions.new
cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller|
cmd_args << "--reverse" if options[:reverse]
cmd_args << "--all" if options[:all]
......@@ -179,7 +177,7 @@ module Redmine
cmd_args << "--since='#{options[:since].strftime("%Y-%m-%d %H:%M:%S")}'" if options[:since]
cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty?
scm_cmd *cmd_args do |io|
scm_cmd cmd_args do |io|
files=[]
changeset = {}
parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
......@@ -255,10 +253,9 @@ module Redmine
revisions << revision
end
end
revisions
end
revisions
rescue ScmCommandAborted
revisions
end
def diff(path, identifier_from, identifier_to=nil)
......@@ -271,14 +268,12 @@ module Redmine
end
cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty?
diff = []
scm_cmd *cmd_args do |io|
scm_cmd cmd_args do |io|
io.each_line do |line|
diff << line
end
diff
end
diff
rescue ScmCommandAborted
nil
end
def annotate(path, identifier=nil)
......@@ -286,34 +281,36 @@ module Redmine
cmd_args = %w|blame|
cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path)
blame = Annotate.new
content = nil
scm_cmd(*cmd_args) { |io| io.binmode; content = io.read }
# git annotates binary files
if content.respond_to?("is_binary_data?") && content.is_binary_data? # Ruby 1.8.x and <1.9.2
return nil
elsif content.respond_to?(:force_encoding) && (content.dup.force_encoding("UTF-8") != content.dup.force_encoding("BINARY")) # Ruby 1.9.2
# TODO: need to handle edge cases of non-binary content that isn't UTF-8
return nil
content = scm_cmd(cmd_args) do |io|
io.binmode
io.read
end
identifier = ''
# git shows commit author on the first occurrence only
authors_by_commit = {}
content.split("\n").each do |line|
if line =~ /^([0-9a-f]{39,40})\s.*/
identifier = $1
elsif line =~ /^author (.+)/
authors_by_commit[identifier] = $1.strip
elsif line =~ /^\t(.*)/
blame.add_line($1, Revision.new(
:identifier => identifier,
:author => authors_by_commit[identifier]))
identifier = ''
author = ''
if content
# git annotates binary files
if content.respond_to?("is_binary_data?") && content.is_binary_data? # Ruby 1.8.x and <1.9.2
return nil
elsif content.respond_to?(:force_encoding) && (content.dup.force_encoding("UTF-8") != content.dup.force_encoding("BINARY")) # Ruby 1.9.2
# TODO: need to handle edge cases of non-binary content that isn't UTF-8
return nil
end
identifier = ''
# git shows commit author on the first occurrence only
authors_by_commit = {}
content.split("\n").each do |line|
if line =~ /^([0-9a-f]{39,40})\s.*/
identifier = $1
elsif line =~ /^author (.+)/
authors_by_commit[identifier] = $1.strip
elsif line =~ /^\t(.*)/
blame.add_line($1, Revision.new(
:identifier => identifier,
:author => authors_by_commit[identifier]))
identifier = ''
author = ''
end
end
blame
end
blame
rescue ScmCommandAborted
nil
end
def cat(path, identifier=nil)
......@@ -322,14 +319,18 @@ module Redmine
end
cmd_args = %w|show --no-color|
cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}"
cat = nil
scm_cmd(*cmd_args) do |io|
scm_cmd(cmd_args) do |io|
io.binmode
cat = io.read
io.read
end
cat
rescue ScmCommandAborted
nil
end
def save_entry_to_temp_file(path, identifier)
f = Tempfile.new(path.split("/").last, File.join(Rails.root, 'tmp'))
cmd_args = %w|show --no-color|
cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}"
scm_cmd(cmd_args, f.path)
f
end
class Revision < Redmine::Scm::Adapters::Revision
......@@ -339,20 +340,27 @@ module Redmine
end
end
def scm_cmd(*args, &block)
private
def scm_cmd(cmd_args, output_path=nil, &block)
cmd = build_scm_cmd(cmd_args)
begin
ret = shellout(cmd, output_path, &block)
rescue Exception => e
logger.error("Error executing Git: #{e.message}")
end
return nil if $? && $?.exitstatus != 0
ret
end
# returns the string that will represent the command for shelling out
def build_scm_cmd(args)
repo_path = root_url || url
full_args = [GIT_BIN, '--git-dir', repo_path]
if self.class.client_version_above?([1, 7, 2])
full_args << '-c' << 'core.quotepath=false'
end
full_args += args
ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block)
if $? && $?.exitstatus != 0
raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
end
ret
(full_args + args).map { |e| shell_quote e.to_s }.join(' ')
end
private :scm_cmd
end
end
end
......
......@@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'redmine/scm/adapters/abstract_adapter'
require_dependency 'redmine/scm/adapters/abstract_adapter'
require 'cgi'
module Redmine
......
......@@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'redmine/scm/adapters/abstract_adapter'
require_dependency 'redmine/scm/adapters/abstract_adapter'
require 'uri'
module Redmine
......@@ -57,194 +57,198 @@ module Redmine
# Get info about the svn repository
def info
cmd = "#{self.class.sq_bin} info --xml #{target}"
cmd << credentials_string
info = nil
shellout(cmd) do |io|
cmd_args = ['info','--xml', target, credentials_string]
scm_cmd(cmd_args) do |io|
output = io.read
if output.respond_to?(:force_encoding)
output.force_encoding('UTF-8')
end
begin
doc = ActiveSupport::XmlMini.parse(output)
#root_url = doc.elements["info/entry/repository/root"].text
info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'],
:lastrev => Revision.new({
:identifier => doc['info']['entry']['commit']['revision'],
:time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime,
:author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "")
})
})
rescue
end
doc = ActiveSupport::XmlMini.parse(output)
#root_url = doc.elements["info/entry/repository/root"].text
Info.new({
:root_url => doc['info']['entry']['repository']['root']['__content__'],
:lastrev => Revision.new({
:identifier => doc['info']['entry']['commit']['revision'],
:time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime,
:author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "")
})
})
end
return nil if $? && $?.exitstatus != 0
info
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 ||= ''
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
identifier = initialize_identifier(identifier)
entries = Entries.new
cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}"
cmd << credentials_string
shellout(cmd) do |io|
cmd_args = ['list', '--xml', "#{target(path)}@#{identifier}", credentials_string]
scm_cmd(cmd_args) do |io|
output = io.read
if output.respond_to?(:force_encoding)
output.force_encoding('UTF-8')
end
begin
doc = ActiveSupport::XmlMini.parse(output)
each_xml_element(doc['lists']['list'], 'entry') do |entry|
commit = entry['commit']
commit_date = commit['date']
# Skip directory if there is no commit date (usually that
# means that we don't have read access to it)
next if entry['kind'] == 'dir' && commit_date.nil?
name = entry['name']['__content__']
entries << Entry.new({:name => URI.unescape(name),
:path => ((path.empty? ? "" : "#{path}/") + name),
:kind => entry['kind'],
:size => ((s = entry['size']) ? s['__content__'].to_i : nil),
:lastrev => Revision.new({
:identifier => commit['revision'],
:time => Time.parse(commit_date['__content__'].to_s).localtime,
:author => ((a = commit['author']) ? a['__content__'] : nil)
})
doc = ActiveSupport::XmlMini.parse(output)
each_xml_element(doc['lists']['list'], 'entry') do |entry|
commit = entry['commit']
commit_date = commit['date']
# Skip directory if there is no commit date (usually that
# means that we don't have read access to it)
next if entry['kind'] == 'dir' && commit_date.nil?
name = entry['name']['__content__']
entries << Entry.new({:name => URI.unescape(name),
:path => ((path.empty? ? "" : "#{path}/") + name),
:kind => entry['kind'],
:size => ((s = entry['size']) ? s['__content__'].to_i : nil),
:lastrev => Revision.new({
:identifier => commit['revision'],
:time => Time.parse(commit_date['__content__'].to_s).localtime,
:author => ((a = commit['author']) ? a['__content__'] : nil)
})
end
rescue Exception => e
logger.error("Error parsing svn output: #{e.message}")
logger.error("Output was:\n #{output}")
})
end
logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
entries.sort_by_name
end
return nil if $? && $?.exitstatus != 0
logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
entries.sort_by_name
end
def properties(path, identifier=nil)
# proplist xml output supported in svn 1.5.0 and higher
return nil unless self.class.client_version_above?([1, 5, 0])
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
cmd = "#{self.class.sq_bin} proplist --verbose --xml #{target(path)}@#{identifier}"
cmd << credentials_string
identifier = initialize_identifier(identifier)
cmd_args = ['proplist', '--verbose', '--xml', "#{target(path)}@#{identifier}", credentials_string]
properties = {}
shellout(cmd) do |io|
scm_cmd(cmd_args) do |io|
output = io.read
if output.respond_to?(:force_encoding)
output.force_encoding('UTF-8')
end
begin
doc = ActiveSupport::XmlMini.parse(output)
each_xml_element(doc['properties']['target'], 'property') do |property|
properties[ property['name'] ] = property['__content__'].to_s
end
rescue
doc = ActiveSupport::XmlMini.parse(output)
each_xml_element(doc['properties']['target'], 'property') do |property|
properties[ property['name'] ] = property['__content__'].to_s
end
properties
end
return nil if $? && $?.exitstatus != 0
properties
end
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path ||= ''
identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1
identifier_from = initialize_identifier(identifier_from)
identifier_to = initialize_identifier(identifier_to, 1)
cmd_args = ['log', '--xml', '-r', "#{identifier_from}:#{identifier_to}", credentials_string]
cmd_args << " --verbose " if options[:with_paths]
cmd_args << " --limit #{options[:limit].to_i}" if options[:limit]
cmd_args << target(path)
revisions = Revisions.new
cmd = "#{self.class.sq_bin} log --xml -r #{identifier_from}:#{identifier_to}"
cmd << credentials_string
cmd << " --verbose " if options[:with_paths]
cmd << " --limit #{options[:limit].to_i}" if options[:limit]
cmd << ' ' + target(path)
shellout(cmd) do |io|
scm_cmd(cmd_args) do |io|
output = io.read
if output.respond_to?(:force_encoding)
output.force_encoding('UTF-8')
end
begin
doc = ActiveSupport::XmlMini.parse(output)
each_xml_element(doc['log'], 'logentry') do |logentry|
paths = []
doc = ActiveSupport::XmlMini.parse(output)
each_xml_element(doc['log'], 'logentry') do |logentry|
paths = []
if logentry['paths'] && logentry['paths']['path']
each_xml_element(logentry['paths'], 'path') do |path|
paths << {:action => path['action'],
:path => path['__content__'],
:from_path => path['copyfrom-path'],
:from_revision => path['copyfrom-rev']
}
end if logentry['paths'] && logentry['paths']['path']
paths.sort! { |x,y| x[:path] <=> y[:path] }
revisions << Revision.new({:identifier => logentry['revision'],
:author => (logentry['author'] ? logentry['author']['__content__'] : ""),
:time => Time.parse(logentry['date']['__content__'].to_s).localtime,
:message => logentry['msg']['__content__'],
:paths => paths
})
end
end
rescue
paths.sort! { |x,y| x[:path] <=> y[:path] }
revisions << Revision.new({:identifier => logentry['revision'],
:author => (logentry['author'] ? logentry['author']['__content__'] : ""),
:time => Time.parse(logentry['date']['__content__'].to_s).localtime,
:message => logentry['msg']['__content__'],
:paths => paths
})
end
revisions
end
return nil if $? && $?.exitstatus != 0
revisions
end
def diff(path, identifier_from, identifier_to=nil, type="inline")
path ||= ''
identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
cmd = "#{self.class.sq_bin} diff -r "
cmd << "#{identifier_to}:"
cmd << "#{identifier_from}"
cmd << " #{target(path)}@#{identifier_from}"
cmd << credentials_string
identifier_from = initialize_identifier(identifier_from, '')
identifier_to = initialize_identifier(identifier_to, identifier_from.to_i - 1)
cmd_args = ["diff -r",
"#{identifier_to}:#{identifier_from}",
"#{target(path)}@#{identifier_from}",
credentials_string]
diff = []
shellout(cmd) do |io|
scm_cmd(cmd_args) do |io|
io.each_line do |line|
diff << line
end
diff
end
return nil if $? && $?.exitstatus != 0
diff
end
def cat(path, identifier=nil)
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
cmd = "#{self.class.sq_bin} cat #{target(path)}@#{identifier}"
cmd << credentials_string
cat = nil
shellout(cmd) do |io|
identifier = initialize_identifier(identifier)
cmd_args = ['cat', "#{target(path)}@#{identifier}", credentials_string]
scm_cmd(cmd_args) do |io|
io.binmode
cat = io.read
io.read
end
return nil if $? && $?.exitstatus != 0
cat
end
def save_entry_to_temp_file(path, identifier)
f = Tempfile.new(path.split("/").last, File.join(Rails.root, 'tmp'))
identifier = initialize_identifier(identifier)
cmd_args = ['cat', "#{target(path)}@#{identifier}", credentials_string]
scm_cmd(cmd_args, f.path)
f
end
def annotate(path, identifier=nil)
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
cmd = "#{self.class.sq_bin} blame #{target(path)}@#{identifier}"
cmd << credentials_string
identifier = initialize_identifier(identifier)
cmd_args = ['blame', "#{target(path)}@#{identifier}", credentials_string]
blame = Annotate.new
shellout(cmd) do |io|
scm_cmd(cmd_args) do |io|
io.each_line do |line|
next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
end
blame
end
return nil if $? && $?.exitstatus != 0
blame
end
private
def initialize_identifier(identifier, default="HEAD")
(identifier && identifier.to_i > 0) ? identifier.to_i : default
end
def scm_cmd(cmd_args, output_path=nil, &block)
cmd = build_scm_cmd(cmd_args)
begin
ret = shellout(cmd, output_path, &block)
rescue Exception => e
logger.error("Error executing SVN: #{e.message}")
end
return nil if $? && $?.exitstatus != 0
ret
end
# returns the string that will represent the command for shelling out
def build_scm_cmd(args)
([ self.class.sq_bin ] + args).join(' ')
end
def credentials_string
str = ''
str << " --username #{shell_quote(@login)}" unless @login.blank?
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment