class Git attr_reader :name attr_reader :base attr_reader :ref def initialize(name, base, ref = 'HEAD', debug = false, cachefile = nil) @name = name @base = base @ref = ref @debug = debug @cachefile = cachefile end def open_cache @cache = File.new(@cachefile, 'a') end def close_cache unless @cache.nil? @cache.close @cache = nil end end def write_cache(commit) obj = Marshal.dump(commit) raise "Object too large" if obj.size > 65535 str = ((obj.size >> 8) & 0xff).chr str += (obj.size & 0xff).chr str += obj @cache.write(str) @cache.flush end def read_cache f = File.new(@cachefile) while(!f.eof?) tmp = f.read(2) len = (tmp[0] << 8) + tmp[1] obj = f.read(len) raise "Read short object" if obj.size != len yield Marshal.load(obj) end f.close end def get_commits(last = nil, &block) if last.nil? range = @ref unless @cachefile.nil? begin read_cache do |commit| block.call(commit) last = commit end range = "#{last[:hash]}..#{@ref}" rescue end end else range = "#{last}..#{@ref}" end open_cache unless @cachefile.nil? commit = nil sh("git log --reverse --summary --numstat --pretty=format:\"HEADER: %at %ai %H %T %aN <%aE>\" #{range}") do |line| if line =~ /^HEADER:/ unless commit.nil? write_cache(commit) unless @cachefile.nil? block.call(commit) end parts = line.split(' ', 8) parts.shift commit = Hash.new commit[:time] = Time.at(parts[0].to_i) commit[:timezone] = parts[3] commit[:hash] = parts[4] commit[:tree] = parts[5] name = nil email = '' match = /^(.+) <(.+)>$/.match(parts[6]) if match.nil? name = parts[6] else name, email = match.captures end commit[:author] = Author.new(name, email) commit[:files_added] = 0 commit[:files_deleted] = 0 commit[:lines_added] = 0 commit[:lines_deleted] = 0 elsif line == '' write_cache(commit) unless @cachefile.nil? block.call(commit) commit = nil elsif line =~ /^ / if line =~ /^ create/ commit[:files_added] += 1 elsif line =~ /^ delete/ commit[:files_deleted] += 1 end else match = /^(\d+)\s+(\d+)/.match(line) unless match.nil? added, deleted = match.captures commit[:lines_added] += added.to_i commit[:lines_deleted] += deleted.to_i end end end unless commit.nil? write_cache(commit) unless @cachefile.nil? block.call(commit) end ensure close_cache unless @cachefile.nil? end def get_files(ref = nil, &block) ref ||= @ref sh("git ls-tree -r -l #{ref}").split(/\n/).each do |line| parts = line.split(/\s+/, 5) next if parts[1] != 'blob' file = Hash.new file[:hash] = parts[2] file[:size] = parts[3].to_i file[:name] = parts[4] block.call(file) end end private def sh(cmd, &block) puts cmd if @debug Dir.chdir(@base) do if block.nil? `#{cmd}` else IO.popen(cmd) do |io| io.each_line do |line| block.call(line.chomp) end end end end end end