annotate lib/redmine/scm/adapters/git_adapter.rb @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +0100
parents 513646585e45
children 1d32c0a0efbf
rev   line source
Chris@0 1 # redMine - project management software
Chris@0 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 require 'redmine/scm/adapters/abstract_adapter'
Chris@0 19
Chris@0 20 module Redmine
Chris@0 21 module Scm
Chris@0 22 module Adapters
Chris@0 23 class GitAdapter < AbstractAdapter
Chris@0 24 # Git executable name
Chris@0 25 GIT_BIN = "git"
Chris@0 26
Chris@0 27 def info
Chris@0 28 begin
Chris@0 29 Info.new(:root_url => url, :lastrev => lastrev('',nil))
Chris@0 30 rescue
Chris@0 31 nil
Chris@0 32 end
Chris@0 33 end
Chris@0 34
Chris@0 35 def branches
Chris@0 36 return @branches if @branches
Chris@0 37 @branches = []
Chris@0 38 cmd = "#{GIT_BIN} --git-dir #{target('')} branch"
Chris@0 39 shellout(cmd) do |io|
Chris@0 40 io.each_line do |line|
Chris@0 41 @branches << line.match('\s*\*?\s*(.*)$')[1]
Chris@0 42 end
Chris@0 43 end
Chris@0 44 @branches.sort!
Chris@0 45 end
Chris@0 46
Chris@0 47 def tags
Chris@0 48 return @tags if @tags
Chris@0 49 cmd = "#{GIT_BIN} --git-dir #{target('')} tag"
Chris@0 50 shellout(cmd) do |io|
Chris@0 51 @tags = io.readlines.sort!.map{|t| t.strip}
Chris@0 52 end
Chris@0 53 end
Chris@0 54
Chris@0 55 def default_branch
Chris@0 56 branches.include?('master') ? 'master' : branches.first
Chris@0 57 end
Chris@0 58
Chris@0 59 def entries(path=nil, identifier=nil)
Chris@0 60 path ||= ''
Chris@0 61 entries = Entries.new
Chris@0 62 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
Chris@0 63 cmd << shell_quote("HEAD:" + path) if identifier.nil?
Chris@0 64 cmd << shell_quote(identifier + ":" + path) if identifier
Chris@0 65 shellout(cmd) do |io|
Chris@0 66 io.each_line do |line|
Chris@0 67 e = line.chomp.to_s
Chris@0 68 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/
Chris@0 69 type = $1
Chris@0 70 sha = $2
Chris@0 71 size = $3
Chris@0 72 name = $4
Chris@0 73 full_path = path.empty? ? name : "#{path}/#{name}"
Chris@0 74 entries << Entry.new({:name => name,
Chris@0 75 :path => full_path,
Chris@0 76 :kind => (type == "tree") ? 'dir' : 'file',
Chris@0 77 :size => (type == "tree") ? nil : size,
Chris@0 78 :lastrev => lastrev(full_path,identifier)
Chris@0 79 }) unless entries.detect{|entry| entry.name == name}
Chris@0 80 end
Chris@0 81 end
Chris@0 82 end
Chris@0 83 return nil if $? && $?.exitstatus != 0
Chris@0 84 entries.sort_by_name
Chris@0 85 end
Chris@0 86
Chris@0 87 def lastrev(path,rev)
Chris@0 88 return nil if path.nil?
Chris@0 89 cmd = "#{GIT_BIN} --git-dir #{target('')} log --pretty=fuller --no-merges -n 1 "
Chris@0 90 cmd << " #{shell_quote rev} " if rev
Chris@0 91 cmd << "-- #{path} " unless path.empty?
Chris@0 92 shellout(cmd) do |io|
Chris@0 93 begin
Chris@0 94 id = io.gets.split[1]
Chris@0 95 author = io.gets.match('Author:\s+(.*)$')[1]
Chris@0 96 2.times { io.gets }
Chris@0 97 time = io.gets.match('CommitDate:\s+(.*)$')[1]
Chris@0 98
Chris@0 99 Revision.new({
Chris@0 100 :identifier => id,
Chris@0 101 :scmid => id,
Chris@0 102 :author => author,
Chris@0 103 :time => time,
Chris@0 104 :message => nil,
Chris@0 105 :paths => nil
Chris@0 106 })
Chris@0 107 rescue NoMethodError => e
Chris@0 108 logger.error("The revision '#{path}' has a wrong format")
Chris@0 109 return nil
Chris@0 110 end
Chris@0 111 end
Chris@0 112 end
Chris@0 113
Chris@0 114 def revisions(path, identifier_from, identifier_to, options={})
Chris@0 115 revisions = Revisions.new
Chris@0 116
Chris@0 117 cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw --date=iso --pretty=fuller"
Chris@0 118 cmd << " --reverse" if options[:reverse]
Chris@0 119 cmd << " --all" if options[:all]
Chris@0 120 cmd << " -n #{options[:limit]} " if options[:limit]
Chris@0 121 cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
Chris@0 122 cmd << " #{shell_quote identifier_to} " if identifier_to
Chris@0 123 cmd << " --since=#{shell_quote(options[:since].strftime("%Y-%m-%d %H:%M:%S"))}" if options[:since]
Chris@0 124 cmd << " -- #{path}" if path && !path.empty?
Chris@0 125
Chris@0 126 shellout(cmd) do |io|
Chris@0 127 files=[]
Chris@0 128 changeset = {}
Chris@0 129 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
Chris@0 130 revno = 1
Chris@0 131
Chris@0 132 io.each_line do |line|
Chris@0 133 if line =~ /^commit ([0-9a-f]{40})$/
Chris@0 134 key = "commit"
Chris@0 135 value = $1
Chris@0 136 if (parsing_descr == 1 || parsing_descr == 2)
Chris@0 137 parsing_descr = 0
Chris@0 138 revision = Revision.new({
Chris@0 139 :identifier => changeset[:commit],
Chris@0 140 :scmid => changeset[:commit],
Chris@0 141 :author => changeset[:author],
Chris@0 142 :time => Time.parse(changeset[:date]),
Chris@0 143 :message => changeset[:description],
Chris@0 144 :paths => files
Chris@0 145 })
Chris@0 146 if block_given?
Chris@0 147 yield revision
Chris@0 148 else
Chris@0 149 revisions << revision
Chris@0 150 end
Chris@0 151 changeset = {}
Chris@0 152 files = []
Chris@0 153 revno = revno + 1
Chris@0 154 end
Chris@0 155 changeset[:commit] = $1
Chris@0 156 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
Chris@0 157 key = $1
Chris@0 158 value = $2
Chris@0 159 if key == "Author"
Chris@0 160 changeset[:author] = value
Chris@0 161 elsif key == "CommitDate"
Chris@0 162 changeset[:date] = value
Chris@0 163 end
Chris@0 164 elsif (parsing_descr == 0) && line.chomp.to_s == ""
Chris@0 165 parsing_descr = 1
Chris@0 166 changeset[:description] = ""
Chris@0 167 elsif (parsing_descr == 1 || parsing_descr == 2) \
Chris@0 168 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
Chris@0 169 parsing_descr = 2
Chris@0 170 fileaction = $1
Chris@0 171 filepath = $2
Chris@0 172 files << {:action => fileaction, :path => filepath}
Chris@0 173 elsif (parsing_descr == 1 || parsing_descr == 2) \
Chris@0 174 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\s+(.+)$/
Chris@0 175 parsing_descr = 2
Chris@0 176 fileaction = $1
Chris@0 177 filepath = $3
Chris@0 178 files << {:action => fileaction, :path => filepath}
Chris@0 179 elsif (parsing_descr == 1) && line.chomp.to_s == ""
Chris@0 180 parsing_descr = 2
Chris@0 181 elsif (parsing_descr == 1)
Chris@0 182 changeset[:description] << line[4..-1]
Chris@0 183 end
Chris@0 184 end
Chris@0 185
Chris@0 186 if changeset[:commit]
Chris@0 187 revision = Revision.new({
Chris@0 188 :identifier => changeset[:commit],
Chris@0 189 :scmid => changeset[:commit],
Chris@0 190 :author => changeset[:author],
Chris@0 191 :time => Time.parse(changeset[:date]),
Chris@0 192 :message => changeset[:description],
Chris@0 193 :paths => files
Chris@0 194 })
Chris@0 195
Chris@0 196 if block_given?
Chris@0 197 yield revision
Chris@0 198 else
Chris@0 199 revisions << revision
Chris@0 200 end
Chris@0 201 end
Chris@0 202 end
Chris@0 203
Chris@0 204 return nil if $? && $?.exitstatus != 0
Chris@0 205 revisions
Chris@0 206 end
Chris@0 207
Chris@0 208 def diff(path, identifier_from, identifier_to=nil)
Chris@0 209 path ||= ''
Chris@0 210
Chris@0 211 if identifier_to
Chris@0 212 cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}"
Chris@0 213 else
Chris@0 214 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}"
Chris@0 215 end
Chris@0 216
Chris@0 217 cmd << " -- #{shell_quote path}" unless path.empty?
Chris@0 218 diff = []
Chris@0 219 shellout(cmd) do |io|
Chris@0 220 io.each_line do |line|
Chris@0 221 diff << line
Chris@0 222 end
Chris@0 223 end
Chris@0 224 return nil if $? && $?.exitstatus != 0
Chris@0 225 diff
Chris@0 226 end
Chris@0 227
Chris@0 228 def annotate(path, identifier=nil)
Chris@0 229 identifier = 'HEAD' if identifier.blank?
Chris@0 230 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}"
Chris@0 231 blame = Annotate.new
Chris@0 232 content = nil
Chris@0 233 shellout(cmd) { |io| io.binmode; content = io.read }
Chris@0 234 return nil if $? && $?.exitstatus != 0
Chris@0 235 # git annotates binary files
Chris@0 236 return nil if content.is_binary_data?
Chris@0 237 identifier = ''
Chris@0 238 # git shows commit author on the first occurrence only
Chris@0 239 authors_by_commit = {}
Chris@0 240 content.split("\n").each do |line|
Chris@0 241 if line =~ /^([0-9a-f]{39,40})\s.*/
Chris@0 242 identifier = $1
Chris@0 243 elsif line =~ /^author (.+)/
Chris@0 244 authors_by_commit[identifier] = $1.strip
Chris@0 245 elsif line =~ /^\t(.*)/
Chris@0 246 blame.add_line($1, Revision.new(:identifier => identifier, :author => authors_by_commit[identifier]))
Chris@0 247 identifier = ''
Chris@0 248 author = ''
Chris@0 249 end
Chris@0 250 end
Chris@0 251 blame
Chris@0 252 end
Chris@0 253
Chris@0 254 def cat(path, identifier=nil)
Chris@0 255 if identifier.nil?
Chris@0 256 identifier = 'HEAD'
Chris@0 257 end
Chris@0 258 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}"
Chris@0 259 cat = nil
Chris@0 260 shellout(cmd) do |io|
Chris@0 261 io.binmode
Chris@0 262 cat = io.read
Chris@0 263 end
Chris@0 264 return nil if $? && $?.exitstatus != 0
Chris@0 265 cat
Chris@0 266 end
Chris@0 267 end
Chris@0 268 end
Chris@0 269 end
Chris@0 270 end