diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/redmine.rb | 2 | ||||
-rw-r--r-- | lib/redmine/scm/adapters/darcs_adapter.rb | 8 | ||||
-rw-r--r-- | lib/redmine/scm/adapters/git_adapter.rb | 261 |
3 files changed, 268 insertions, 3 deletions
diff --git a/lib/redmine.rb b/lib/redmine.rb index e76d77e9e..4c5cbdaae 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -10,7 +10,7 @@ rescue LoadError # RMagick is not available end -REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar ) +REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git ) # Permissions Redmine::AccessControl.map do |map| diff --git a/lib/redmine/scm/adapters/darcs_adapter.rb b/lib/redmine/scm/adapters/darcs_adapter.rb index 2955b26dc..cd8610121 100644 --- a/lib/redmine/scm/adapters/darcs_adapter.rb +++ b/lib/redmine/scm/adapters/darcs_adapter.rb @@ -102,8 +102,12 @@ module Redmine def diff(path, identifier_from, identifier_to=nil, type="inline") path = '*' if path.blank? cmd = "#{DARCS_BIN} diff --repodir #{@url}" - cmd << " --to-match \"hash #{identifier_from}\"" - cmd << " --from-match \"hash #{identifier_to}\"" if identifier_to + if identifier_to.nil? + cmd << " --match \"hash #{identifier_from}\"" + else + cmd << " --to-match \"hash #{identifier_from}\"" + cmd << " --from-match \"hash #{identifier_to}\"" + end cmd << " -u #{path}" diff = [] shellout(cmd) do |io| diff --git a/lib/redmine/scm/adapters/git_adapter.rb b/lib/redmine/scm/adapters/git_adapter.rb new file mode 100644 index 000000000..b6b1b858d --- /dev/null +++ b/lib/redmine/scm/adapters/git_adapter.rb @@ -0,0 +1,261 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/abstract_adapter' + +module Redmine + module Scm + module Adapters + class GitAdapter < AbstractAdapter + + # Git executable name + GIT_BIN = "git" + + # Get the revision of a particuliar file + def get_rev (rev,path) + cmd="git --git-dir #{target('')} show #{shell_quote rev} -- #{shell_quote path}" if rev!='latest' and (! rev.nil?) + cmd="git --git-dir #{target('')} log -1 master -- #{shell_quote path}" if + rev=='latest' or rev.nil? + rev=[] + i=0 + shellout(cmd) do |io| + files=[] + changeset = {} + parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files + + io.each_line do |line| + if line =~ /^commit ([0-9a-f]{40})$/ + key = "commit" + value = $1 + if (parsing_descr == 1 || parsing_descr == 2) + parsing_descr = 0 + rev = Revision.new({:identifier => changeset[:commit], + :scmid => changeset[:commit], + :author => changeset[:author], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => files + }) + changeset = {} + files = [] + end + changeset[:commit] = $1 + elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/ + key = $1 + value = $2 + if key == "Author" + changeset[:author] = value + elsif key == "Date" + changeset[:date] = value + end + elsif (parsing_descr == 0) && line.chomp.to_s == "" + parsing_descr = 1 + changeset[:description] = "" + elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/ + parsing_descr = 2 + fileaction = $1 + filepath = $2 + files << {:action => fileaction, :path => filepath} + elsif (parsing_descr == 1) && line.chomp.to_s == "" + parsing_descr = 2 + elsif (parsing_descr == 1) + changeset[:description] << line + end + end + rev = Revision.new({:identifier => changeset[:commit], + :scmid => changeset[:commit], + :author => changeset[:author], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => files + }) + + end + + get_rev('latest',path) if rev == [] + + return nil if $? && $?.exitstatus != 0 + return rev + end + + + def info + root_url = target('') + info = Info.new({:root_url => target(''), + :lastrev => revisions(root_url,nil,nil,{:limit => 1}).first + }) + info + rescue Errno::ENOENT => e + return nil + end + + def entries(path=nil, identifier=nil) + path ||= '' + entries = Entries.new + cmd = "#{GIT_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| + io.each_line do |line| + e = line.chomp.to_s + if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/ + type = $1 + sha = $2 + size = $3 + name = $4 + entries << Entry.new({:name => name, + :path => (path.empty? ? name : "#{path}/#{name}"), + :kind => ((type == "tree") ? 'dir' : 'file'), + :size => ((type == "tree") ? nil : size), + :lastrev => get_rev(identifier,(path.empty? ? name : "#{path}/#{name}")) + + }) unless entries.detect{|entry| entry.name == name} + end + end + end + return nil if $? && $?.exitstatus != 0 + entries.sort_by_name + end + + def entry(path=nil, identifier=nil) + path ||= '' + search_path = path.split('/')[0..-2].join('/') + entry_name = path.split('/').last + e = entries(search_path, identifier) + e ? e.detect{|entry| entry.name == entry_name} : nil + end + + def revisions(path, identifier_from, identifier_to, options={}) + revisions = Revisions.new + cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw " + cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit] + cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from + cmd << " #{shell_quote identifier_to} " if identifier_to + #cmd << " HEAD " if !identifier_to + shellout(cmd) do |io| + files=[] + changeset = {} + parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files + revno = 1 + + io.each_line do |line| + if line =~ /^commit ([0-9a-f]{40})$/ + key = "commit" + value = $1 + if (parsing_descr == 1 || parsing_descr == 2) + parsing_descr = 0 + revisions << Revision.new({:identifier => changeset[:commit], + :scmid => changeset[:commit], + :author => changeset[:author], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => files + }) + changeset = {} + files = [] + revno = revno + 1 + end + changeset[:commit] = $1 + elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/ + key = $1 + value = $2 + if key == "Author" + changeset[:author] = value + elsif key == "Date" + changeset[:date] = value + end + elsif (parsing_descr == 0) && line.chomp.to_s == "" + parsing_descr = 1 + changeset[:description] = "" + elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/ + parsing_descr = 2 + fileaction = $1 + filepath = $2 + files << {:action => fileaction, :path => filepath} + elsif (parsing_descr == 1) && line.chomp.to_s == "" + parsing_descr = 2 + elsif (parsing_descr == 1) + changeset[:description] << line[4..-1] + end + end + + revisions << Revision.new({:identifier => changeset[:commit], + :scmid => changeset[:commit], + :author => changeset[:author], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => files + }) if changeset[:commit] + + end + + return nil if $? && $?.exitstatus != 0 + revisions + end + + def diff(path, identifier_from, identifier_to=nil, type="inline") + path ||= '' + if !identifier_to + identifier_to = nil + end + + cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}" if identifier_to.nil? + cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}" if !identifier_to.nil? + cmd << " -- #{shell_quote path}" unless path.empty? + diff = [] + shellout(cmd) do |io| + io.each_line do |line| + diff << line + end + end + return nil if $? && $?.exitstatus != 0 + DiffTableList.new diff, type + end + + def annotate(path, identifier=nil) + identifier = 'HEAD' if identifier.blank? + cmd = "#{GIT_BIN} --git-dir #{target('')} blame -l #{shell_quote identifier} -- #{shell_quote path}" + blame = Annotate.new + shellout(cmd) do |io| + io.each_line do |line| + next unless line =~ /([0-9a-f]{39,40})\s\((\w*)[^\)]*\)(.*)$/ + blame.add_line($3.rstrip, Revision.new(:identifier => $1, :author => $2.strip)) + end + end + return nil if $? && $?.exitstatus != 0 + blame + end + + def cat(path, identifier=nil) + if identifier.nil? + identifier = 'HEAD' + end + cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}" + cat = nil + shellout(cmd) do |io| + io.binmode + cat = io.read + end + return nil if $? && $?.exitstatus != 0 + cat + end + end + end + end + +end + |