From d07a2c04c1599fe707831afdd29397cc36e02fa6 Mon Sep 17 00:00:00 2001 From: scott Chacon Date: Tue, 20 Nov 2007 12:07:46 -0800 Subject: added files from the gitrb project, which seems abandoned, but which is great code --- lib/git/raw/git.rb | 63 +++++++++ lib/git/raw/internal/loose.rb | 96 ++++++++++++++ lib/git/raw/internal/mmap.rb | 44 +++++++ lib/git/raw/internal/object.rb | 23 ++++ lib/git/raw/internal/pack.rb | 240 ++++++++++++++++++++++++++++++++++ lib/git/raw/object.rb | 268 ++++++++++++++++++++++++++++++++++++++ tests/units/test_raw_internals.rb | 15 +++ 7 files changed, 749 insertions(+) create mode 100644 lib/git/raw/git.rb create mode 100644 lib/git/raw/internal/loose.rb create mode 100644 lib/git/raw/internal/mmap.rb create mode 100644 lib/git/raw/internal/object.rb create mode 100644 lib/git/raw/internal/pack.rb create mode 100644 lib/git/raw/object.rb create mode 100644 tests/units/test_raw_internals.rb diff --git a/lib/git/raw/git.rb b/lib/git/raw/git.rb new file mode 100644 index 0000000..004e795 --- /dev/null +++ b/lib/git/raw/git.rb @@ -0,0 +1,63 @@ +require 'git/internal/object' +require 'git/internal/pack' +require 'git/internal/loose' +require 'git/object' + +module Git + class Repository + def initialize(git_dir) + @git_dir = git_dir + @loose = Internal::LooseStorage.new(git_path("objects")) + @packs = [] + initpacks + end + + def git_path(path) + return "#@git_dir/#{path}" + end + + def get_object_by_sha1(sha1) + r = get_raw_object_by_sha1(sha1) + return nil if !r + Object.from_raw(r, self) + end + + def get_raw_object_by_sha1(sha1) + sha1 = [sha1].pack("H*") + + # try packs + @packs.each do |pack| + o = pack[sha1] + return o if o + end + + # try loose storage + o = @loose[sha1] + return o if o + + # try packs again, maybe the object got packed in the meantime + initpacks + @packs.each do |pack| + o = pack[sha1] + return o if o + end + + nil + end + + def initpacks + @packs.each do |pack| + pack.close + end + @packs = [] + Dir.open(git_path("objects/pack/")) do |dir| + dir.each do |entry| + if entry =~ /\.pack$/i + @packs << Git::Internal::PackStorage.new(git_path("objects/pack/" \ + + entry)) + end + end + end + end + end +end diff --git a/lib/git/raw/internal/loose.rb b/lib/git/raw/internal/loose.rb new file mode 100644 index 0000000..0e4020c --- /dev/null +++ b/lib/git/raw/internal/loose.rb @@ -0,0 +1,96 @@ +require 'zlib' +require 'digest/sha1' + +require 'git/raw/internal/object' + +module Git module Raw module Internal + class LooseObjectError < StandardError + end + + class LooseStorage + def initialize(directory) + @directory = directory + end + + def [](sha1) + sha1 = sha1.unpack("H*")[0] + + path = @directory+'/'+sha1[0...2]+'/'+sha1[2..40] + begin + get_raw_object(File.read(path)) + rescue Errno::ENOENT + nil + end + end + + def get_raw_object(buf) + if buf.length < 2 + raise LooseObjectError, "object file too small" + end + + if legacy_loose_object?(buf) + content = Zlib::Inflate.inflate(buf) + header, content = content.split(/\0/, 2) + if !header || !content + raise LooseObjectError, "invalid object header" + end + type, size = header.split(/ /, 2) + if !%w(blob tree commit tag).include?(type) || size !~ /^\d+$/ + raise LooseObjectError, "invalid object header" + end + type = type.to_sym + size = size.to_i + else + type, size, used = unpack_object_header_gently(buf) + content = Zlib::Inflate.inflate(buf[used..-1]) + end + raise LooseObjectError, "size mismatch" if content.length != size + return RawObject.new(type, content) + end + + # private + def unpack_object_header_gently(buf) + used = 0 + c = buf[used] + used += 1 + + type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while c & 0x80 != 0 + if buf.length <= used + raise LooseObjectError, "object file too short" + end + c = buf[used] + used += 1 + + size += (c & 0x7f) << shift + shift += 7 + end + type = OBJ_TYPES[type] + if ![:blob, :tree, :commit, :tag].include?(type) + raise LooseObjectError, "invalid loose object type" + end + return [type, size, used] + end + private :unpack_object_header_gently + + def legacy_loose_object?(buf) + word = (buf[0] << 8) + buf[1] + buf[0] == 0x78 && word % 31 == 0 + end + private :legacy_loose_object? + end +end end + +if $0 == __FILE__ + require 'find' + ARGV.each do |path| + storage = Git::Internal::LooseStorage.new(path) + Find.find(path) do |p| + next if !/\/([0-9a-f]{2})\/([0-9a-f]{38})$/.match(p) + obj = storage[[$1+$2].pack("H*")] + puts "%s %s" % [obj.sha1.unpack("H*")[0], obj.type] + end + end +end diff --git a/lib/git/raw/internal/mmap.rb b/lib/git/raw/internal/mmap.rb new file mode 100644 index 0000000..d7390b1 --- /dev/null +++ b/lib/git/raw/internal/mmap.rb @@ -0,0 +1,44 @@ +begin + require 'mmap' +rescue LoadError + +module Git module Raw module Internal + class Mmap + def initialize(file) + @file = file + @offset = nil + end + + def unmap + @file = nil + end + + def [](*idx) + idx = idx[0] if idx.length == 1 + case idx + when Range + offset = idx.first + len = idx.last - idx.first + idx.exclude_end? ? 0 : 1 + when Fixnum + offset = idx + len = nil + when Array + offset, len = idx + else + raise RuntimeError, "invalid index param: #{idx.class}" + end + if @offset != offset + @file.seek(offset) + end + @offset = offset + len ? len : 1 + if not len + @file.read(1)[0] + else + @file.read(len) + end + end + end +end end + +end # rescue LoadError + diff --git a/lib/git/raw/internal/object.rb b/lib/git/raw/internal/object.rb new file mode 100644 index 0000000..b81df2b --- /dev/null +++ b/lib/git/raw/internal/object.rb @@ -0,0 +1,23 @@ +require 'digest/sha1' + +module Git module Raw module Internal + OBJ_NONE = 0 + OBJ_COMMIT = 1 + OBJ_TREE = 2 + OBJ_BLOB = 3 + OBJ_TAG = 4 + + OBJ_TYPES = [nil, :commit, :tree, :blob, :tag].freeze + + class RawObject + attr_accessor :type, :content + def initialize(type, content) + @type = type + @content = content + end + + def sha1 + Digest::SHA1.digest("%s %d\0" % [@type, @content.length] + @content) + end + end +end end diff --git a/lib/git/raw/internal/pack.rb b/lib/git/raw/internal/pack.rb new file mode 100644 index 0000000..edfeada --- /dev/null +++ b/lib/git/raw/internal/pack.rb @@ -0,0 +1,240 @@ +require 'zlib' +require 'git/raw/internal/object' +require 'git/raw/internal/mmap' + +module Git module Raw module Internal + class PackFormatError < StandardError + end + + class PackStorage + OBJ_OFS_DELTA = 6 + OBJ_REF_DELTA = 7 + + FanOutCount = 256 + SHA1Size = 20 + IdxOffsetSize = 4 + OffsetSize = 4 + OffsetStart = FanOutCount * IdxOffsetSize + SHA1Start = OffsetStart + OffsetSize + EntrySize = OffsetSize + SHA1Size + + def initialize(file) + if file =~ /\.idx$/ + file = file[0...-3] + 'pack' + end + + @name = file + @packfile = File.open(file) + @idxfile = File.open(file[0...-4]+'idx') + @idx = Mmap.new(@idxfile) + + @offsets = [0] + FanOutCount.times do |i| + pos = @idx[i * IdxOffsetSize,IdxOffsetSize].unpack('N')[0] + if pos < @offsets[i] + raise PackFormatError, "pack #@name has discontinuous index #{i}" + end + @offsets << pos + end + + @size = @offsets[-1] + end + + def close + @packfile.close + @idx.unmap + @idxfile.close + end + + def [](sha1) + offset = find_object(sha1) + return nil if !offset + return parse_object(offset) + end + + def each_entry + pos = OffsetStart + @size.times do + offset = @idx[pos,OffsetSize].unpack('N')[0] + sha1 = @idx[pos+OffsetSize,SHA1Size] + pos += EntrySize + yield sha1, offset + end + end + + def each_sha1 + # unpacking the offset is quite expensive, so + # we avoid using #each + pos = SHA1Start + @size.times do + sha1 = @idx[pos,SHA1Size] + pos += EntrySize + yield sha1 + end + end + + def find_object(sha1) + slot = sha1[0] + first, last = @offsets[slot,2] + while first < last + mid = (first + last) / 2 + midsha1 = @idx[SHA1Start + mid * EntrySize,SHA1Size] + cmp = midsha1 <=> sha1 + + if cmp < 0 + first = mid + 1 + elsif cmp > 0 + last = mid + else + pos = OffsetStart + mid * EntrySize + offset = @idx[pos,OffsetSize].unpack('N')[0] + return offset + end + end + + nil + end + private :find_object + + def parse_object(offset) + data, type = unpack_object(offset) + RawObject.new(OBJ_TYPES[type], data) + end + protected :parse_object + + def unpack_object(offset) + obj_offset = offset + @packfile.seek(offset) + + c = @packfile.read(1)[0] + size = c & 0xf + type = (c >> 4) & 7 + shift = 4 + offset += 1 + while c & 0x80 != 0 + c = @packfile.read(1)[0] + size |= ((c & 0x7f) << shift) + shift += 7 + offset += 1 + end + + case type + when OBJ_OFS_DELTA, OBJ_REF_DELTA + data, type = unpack_deltified(type, offset, obj_offset, size) + when OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG + data = unpack_compressed(offset, size) + else + raise PackFormatError, "invalid type #{type}" + end + [data, type] + end + private :unpack_object + + def unpack_deltified(type, offset, obj_offset, size) + @packfile.seek(offset) + data = @packfile.read(SHA1Size) + + if type == OBJ_OFS_DELTA + i = 0 + c = data[i] + base_offset = c & 0x7f + while c & 0x80 != 0 + c = data[i += 1] + base_offset += 1 + base_offset <<= 7 + base_offset |= c & 0x7f + end + base_offset = obj_offset - base_offset + offset += i + 1 + else + base_offset = find_object(data) + offset += SHA1Size + end + + base, type = unpack_object(base_offset) + delta = unpack_compressed(offset, size) + [patch_delta(base, delta), type] + end + private :unpack_deltified + + def unpack_compressed(offset, destsize) + outdata = "" + @packfile.seek(offset) + zstr = Zlib::Inflate.new + while outdata.size < destsize + indata = @packfile.read(4096) + if indata.size == 0 + raise PackFormatError, 'error reading pack data' + end + outdata += zstr.inflate(indata) + end + if outdata.size > destsize + raise PackFormatError, 'error reading pack data' + end + zstr.close + outdata + end + private :unpack_compressed + + def patch_delta(base, delta) + src_size, pos = patch_delta_header_size(delta, 0) + if src_size != base.size + raise PackFormatError, 'invalid delta data' + end + + dest_size, pos = patch_delta_header_size(delta, pos) + dest = "" + while pos < delta.size + c = delta[pos] + pos += 1 + if c & 0x80 != 0 + pos -= 1 + cp_off = cp_size = 0 + cp_off = delta[pos += 1] if c & 0x01 != 0 + cp_off |= delta[pos += 1] << 8 if c & 0x02 != 0 + cp_off |= delta[pos += 1] << 16 if c & 0x04 != 0 + cp_off |= delta[pos += 1] << 24 if c & 0x08 != 0 + cp_size = delta[pos += 1] if c & 0x10 != 0 + cp_size |= delta[pos += 1] << 8 if c & 0x20 != 0 + cp_size |= delta[pos += 1] << 16 if c & 0x40 != 0 + cp_size = 0x10000 if cp_size == 0 + pos += 1 + dest += base[cp_off,cp_size] + elsif c != 0 + dest += delta[pos,c] + pos += c + else + raise PackFormatError, 'invalid delta data' + end + end + dest + end + private :patch_delta + + def patch_delta_header_size(delta, pos) + size = 0 + shift = 0 + begin + c = delta[pos] + if c == nil + raise PackFormatError, 'invalid delta header' + end + pos += 1 + size |= (c & 0x7f) << shift + shift += 7 + end while c & 0x80 != 0 + [size, pos] + end + private :patch_delta_header_size + end +end end + +if $0 == __FILE__ + ARGV.each do |path| + storage = Git::Internal::PackStorage.new(path) + storage.each_sha1 do |sha1| + obj = storage[sha1] + puts "%s %s" % [obj.sha1.unpack('H*'), obj.type] + end + end +end diff --git a/lib/git/raw/object.rb b/lib/git/raw/object.rb new file mode 100644 index 0000000..7e3e618 --- /dev/null +++ b/lib/git/raw/object.rb @@ -0,0 +1,268 @@ +require 'digest/sha1' + +module Git + module Raw + + # class for author/committer/tagger lines + class UserInfo + attr_accessor :name, :email, :date, :offset + + def initialize(str) + m = /^(.*?) <(.*)> (\d+) ([+-])0*(\d+?)$/.match(str) + if !m + raise RuntimeError, "invalid %s header in commit" % key + end + @name = m[1] + @email = m[2] + @date = Time.at(Integer(m[3])) + @offset = (m[4] == "-" ? -1 : 1)*Integer(m[5]) + end + + def to_s + "%s <%s> %s %+05d" % [@name, @email, @date.to_i, @offset] + end + end + + # base class for all git objects (blob, tree, commit, tag) + class Object + attr_accessor :repository + + def Object.from_raw(rawobject, repository = nil) + case rawobject.type + when :blob + return Blob.from_raw(rawobject, repository) + when :tree + return Tree.from_raw(rawobject, repository) + when :commit + return Commit.from_raw(rawobject, repository) + when :tag + return Tag.from_raw(rawobject, repository) + else + raise RuntimeError, "got invalid object-type" + end + end + + def initialize + raise NotImplemented, "abstract class" + end + + def type + raise NotImplemented, "abstract class" + end + + def raw_content + raise NotImplemented, "abstract class" + end + + def sha1 + Digest::SHA1.hexdigest("%s %d\0" % \ + [self.type, self.raw_content.length] + \ + self.raw_content) + end + end + + class Blob < Object + attr_accessor :content + + def self.from_raw(rawobject, repository) + new(rawobject.content) + end + + def initialize(content, repository=nil) + @content = content + @repository = repository + end + + def type + :blob + end + + def raw_content + @content + end + end + + class DirectoryEntry + S_IFMT = 00170000 + S_IFLNK = 0120000 + S_IFREG = 0100000 + S_IFDIR = 0040000 + + attr_accessor :mode, :name, :sha1 + def initialize(buf) + m = /^(\d+) (.*)\0(.{20})$/m.match(buf) + if !m + raise RuntimeError, "invalid directory entry" + end + @mode = 0 + m[1].each_byte do |i| + @mode = (@mode << 3) | (i-'0'[0]) + end + @name = m[2] + @sha1 = m[3].unpack("H*")[0] + + if ![S_IFLNK, S_IFDIR, S_IFREG].include?(@mode & S_IFMT) + raise RuntimeError, "unknown type for directory entry" + end + end + + def type + case @mode & S_IFMT + when S_IFLNK + @type = :link + when S_IFDIR + @type = :directory + when S_IFREG + @type = :file + else + raise RuntimeError, "unknown type for directory entry" + end + end + + def type=(type) + case @type + when :link + @mode = (@mode & ~S_IFMT) | S_IFLNK + when :directory + @mode = (@mode & ~S_IFMT) | S_IFDIR + when :file + @mode = (@mode & ~S_IFMT) | S_IFREG + else + raise RuntimeError, "invalid type" + end + end + + def raw + "%o %s\0%s" % [@mode, @name, [@sha1].pack("H*")] + end + end + + class Tree < Object + attr_accessor :entry + + def self.from_raw(rawobject, repository=nil) + entries = [] + rawobject.content.scan(/\d+ .*?\0.{20}/m) do |raw| + entries << DirectoryEntry.new(raw) + end + new(entries, repository) + end + + def initialize(entries=[], repository = nil) + @entry = entries + @repository = repository + end + + def type + :tree + end + + def raw_content + # TODO: sort correctly + #@entry.sort { |a,b| a.name <=> b.name }. + @entry. + collect { |e| e.raw }.join + end + end + + class Commit < Object + attr_accessor :author, :committer, :tree, :parent, :message + + def self.from_raw(rawobject, repository=nil) + parent = [] + tree = author = committer = nil + + headers, message = rawobject.content.split(/\n\n/, 2) + headers = headers.split(/\n/).map { |header| header.split(/ /, 2) } + headers.each do |key, value| + case key + when "tree" + tree = value + when "parent" + parent.push(value) + when "author" + author = UserInfo.new(value) + when "committer" + committer = UserInfo.new(value) + else + warn "unknown header '%s' in commit %s" % \ + [key, rawobject.sha1.unpack("H*")[0]] + end + end + if not tree && author && committer + raise RuntimeError, "incomplete raw commit object" + end + new(tree, parent, author, committer, message, repository) + end + + def initialize(tree, parent, author, committer, message, repository=nil) + @tree = tree + @author = author + @parent = parent + @committer = committer + @message = message + @repository = repository + end + + def type + :commit + end + + def raw_content + "tree %s\n%sauthor %s\ncommitter %s\n\n" % [ + @tree, + @parent.collect { |i| "parent %s\n" % i }.join, + @author, @committer] + @message + end + end + + class Tag < Object + attr_accessor :object, :type, :tag, :tagger, :message + + def self.from_raw(rawobject, repository=nil) + headers, message = rawobject.content.split(/\n\n/, 2) + headers = headers.split(/\n/).map { |header| header.split(/ /, 2) } + headers.each do |key, value| + case key + when "object" + object = value + when "type" + if !["blob", "tree", "commit", "tag"].include?(value) + raise RuntimeError, "invalid type in tag" + end + type = value.to_sym + when "tag" + tag = value + when "tagger" + tagger = UserInfo.new(value) + else + warn "unknown header '%s' in tag" % \ + [key, rawobject.sha1.unpack("H*")[0]] + end + if not object && type && tag && tagger + raise RuntimeError, "incomplete raw tag object" + end + end + new(object, type, tag, tagger, repository) + end + + def initialize(object, type, tag, tagger, repository=nil) + @object = object + @type = type + @tag = tag + @tagger = tagger + @repository = repository + end + + def raw_content + "object %s\ntype %s\ntag %s\ntagger %s\n\n" % \ + [@object, @type, @tag, @tagger] + @message + end + + def type + :tag + end + end + +end +end \ No newline at end of file diff --git a/tests/units/test_raw_internals.rb b/tests/units/test_raw_internals.rb new file mode 100644 index 0000000..b135e52 --- /dev/null +++ b/tests/units/test_raw_internals.rb @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../test_helper' + +class TestRawInternals < Test::Unit::TestCase + + def setup + set_file_paths + @git = Git.open(@wdir) + end + + def test_raw_log + end + +end \ No newline at end of file -- cgit From 90dea6d415bfc5734bc87c2797b26cca311246bc Mon Sep 17 00:00:00 2001 From: scott Chacon Date: Tue, 20 Nov 2007 13:24:44 -0800 Subject: have the pure ruby bindings working to some degree --- lib/git.rb | 3 + lib/git/raw/git.rb | 63 ------ lib/git/raw/internal/loose.rb | 138 ++++++------ lib/git/raw/internal/mmap.rb | 70 ++++--- lib/git/raw/internal/object.rb | 38 ++-- lib/git/raw/internal/pack.rb | 430 +++++++++++++++++++------------------- lib/git/raw/object.rb | 2 +- tests/units/test_raw_internals.rb | 22 +- 8 files changed, 373 insertions(+), 393 deletions(-) delete mode 100644 lib/git/raw/git.rb diff --git a/lib/git.rb b/lib/git.rb index 02a3b8a..49a61ea 100644 --- a/lib/git.rb +++ b/lib/git.rb @@ -23,6 +23,9 @@ require 'git/diff' require 'git/status' require 'git/author' +require 'git/raw/repository' + + # Git/Ruby Library # # This provides bindings for working with git in complex diff --git a/lib/git/raw/git.rb b/lib/git/raw/git.rb deleted file mode 100644 index 004e795..0000000 --- a/lib/git/raw/git.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'git/internal/object' -require 'git/internal/pack' -require 'git/internal/loose' -require 'git/object' - -module Git - class Repository - def initialize(git_dir) - @git_dir = git_dir - @loose = Internal::LooseStorage.new(git_path("objects")) - @packs = [] - initpacks - end - - def git_path(path) - return "#@git_dir/#{path}" - end - - def get_object_by_sha1(sha1) - r = get_raw_object_by_sha1(sha1) - return nil if !r - Object.from_raw(r, self) - end - - def get_raw_object_by_sha1(sha1) - sha1 = [sha1].pack("H*") - - # try packs - @packs.each do |pack| - o = pack[sha1] - return o if o - end - - # try loose storage - o = @loose[sha1] - return o if o - - # try packs again, maybe the object got packed in the meantime - initpacks - @packs.each do |pack| - o = pack[sha1] - return o if o - end - - nil - end - - def initpacks - @packs.each do |pack| - pack.close - end - @packs = [] - Dir.open(git_path("objects/pack/")) do |dir| - dir.each do |entry| - if entry =~ /\.pack$/i - @packs << Git::Internal::PackStorage.new(git_path("objects/pack/" \ - + entry)) - end - end - end - end - end -end diff --git a/lib/git/raw/internal/loose.rb b/lib/git/raw/internal/loose.rb index 0e4020c..d8ec6fb 100644 --- a/lib/git/raw/internal/loose.rb +++ b/lib/git/raw/internal/loose.rb @@ -3,85 +3,89 @@ require 'digest/sha1' require 'git/raw/internal/object' -module Git module Raw module Internal - class LooseObjectError < StandardError - end +module Git + module Raw + module Internal + class LooseObjectError < StandardError + end - class LooseStorage - def initialize(directory) - @directory = directory - end + class LooseStorage + def initialize(directory) + @directory = directory + end - def [](sha1) - sha1 = sha1.unpack("H*")[0] + def [](sha1) + sha1 = sha1.unpack("H*")[0] - path = @directory+'/'+sha1[0...2]+'/'+sha1[2..40] - begin - get_raw_object(File.read(path)) - rescue Errno::ENOENT - nil - end - end + path = @directory+'/'+sha1[0...2]+'/'+sha1[2..40] + begin + get_raw_object(File.read(path)) + rescue Errno::ENOENT + nil + end + end - def get_raw_object(buf) - if buf.length < 2 - raise LooseObjectError, "object file too small" - end + def get_raw_object(buf) + if buf.length < 2 + raise LooseObjectError, "object file too small" + end - if legacy_loose_object?(buf) - content = Zlib::Inflate.inflate(buf) - header, content = content.split(/\0/, 2) - if !header || !content - raise LooseObjectError, "invalid object header" - end - type, size = header.split(/ /, 2) - if !%w(blob tree commit tag).include?(type) || size !~ /^\d+$/ - raise LooseObjectError, "invalid object header" + if legacy_loose_object?(buf) + content = Zlib::Inflate.inflate(buf) + header, content = content.split(/\0/, 2) + if !header || !content + raise LooseObjectError, "invalid object header" + end + type, size = header.split(/ /, 2) + if !%w(blob tree commit tag).include?(type) || size !~ /^\d+$/ + raise LooseObjectError, "invalid object header" + end + type = type.to_sym + size = size.to_i + else + type, size, used = unpack_object_header_gently(buf) + content = Zlib::Inflate.inflate(buf[used..-1]) + end + raise LooseObjectError, "size mismatch" if content.length != size + return RawObject.new(type, content) end - type = type.to_sym - size = size.to_i - else - type, size, used = unpack_object_header_gently(buf) - content = Zlib::Inflate.inflate(buf[used..-1]) - end - raise LooseObjectError, "size mismatch" if content.length != size - return RawObject.new(type, content) - end - # private - def unpack_object_header_gently(buf) - used = 0 - c = buf[used] - used += 1 + # private + def unpack_object_header_gently(buf) + used = 0 + c = buf[used] + used += 1 + + type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while c & 0x80 != 0 + if buf.length <= used + raise LooseObjectError, "object file too short" + end + c = buf[used] + used += 1 - type = (c >> 4) & 7; - size = c & 15; - shift = 4; - while c & 0x80 != 0 - if buf.length <= used - raise LooseObjectError, "object file too short" + size += (c & 0x7f) << shift + shift += 7 + end + type = OBJ_TYPES[type] + if ![:blob, :tree, :commit, :tag].include?(type) + raise LooseObjectError, "invalid loose object type" + end + return [type, size, used] end - c = buf[used] - used += 1 + private :unpack_object_header_gently - size += (c & 0x7f) << shift - shift += 7 - end - type = OBJ_TYPES[type] - if ![:blob, :tree, :commit, :tag].include?(type) - raise LooseObjectError, "invalid loose object type" + def legacy_loose_object?(buf) + word = (buf[0] << 8) + buf[1] + buf[0] == 0x78 && word % 31 == 0 + end + private :legacy_loose_object? end - return [type, size, used] - end - private :unpack_object_header_gently - - def legacy_loose_object?(buf) - word = (buf[0] << 8) + buf[1] - buf[0] == 0x78 && word % 31 == 0 - end - private :legacy_loose_object? + end end -end end +end if $0 == __FILE__ require 'find' diff --git a/lib/git/raw/internal/mmap.rb b/lib/git/raw/internal/mmap.rb index d7390b1..15b5628 100644 --- a/lib/git/raw/internal/mmap.rb +++ b/lib/git/raw/internal/mmap.rb @@ -2,43 +2,47 @@ begin require 'mmap' rescue LoadError -module Git module Raw module Internal - class Mmap - def initialize(file) - @file = file - @offset = nil - end +module Git + module Raw + module Internal + class Mmap + def initialize(file) + @file = file + @offset = nil + end - def unmap - @file = nil - end + def unmap + @file = nil + end - def [](*idx) - idx = idx[0] if idx.length == 1 - case idx - when Range - offset = idx.first - len = idx.last - idx.first + idx.exclude_end? ? 0 : 1 - when Fixnum - offset = idx - len = nil - when Array - offset, len = idx - else - raise RuntimeError, "invalid index param: #{idx.class}" - end - if @offset != offset - @file.seek(offset) - end - @offset = offset + len ? len : 1 - if not len - @file.read(1)[0] - else - @file.read(len) + def [](*idx) + idx = idx[0] if idx.length == 1 + case idx + when Range + offset = idx.first + len = idx.last - idx.first + idx.exclude_end? ? 0 : 1 + when Fixnum + offset = idx + len = nil + when Array + offset, len = idx + else + raise RuntimeError, "invalid index param: #{idx.class}" + end + if @offset != offset + @file.seek(offset) + end + @offset = offset + len ? len : 1 + if not len + @file.read(1)[0] + else + @file.read(len) + end + end end end - end -end end + end +end end # rescue LoadError diff --git a/lib/git/raw/internal/object.rb b/lib/git/raw/internal/object.rb index b81df2b..7f95685 100644 --- a/lib/git/raw/internal/object.rb +++ b/lib/git/raw/internal/object.rb @@ -1,23 +1,27 @@ require 'digest/sha1' -module Git module Raw module Internal - OBJ_NONE = 0 - OBJ_COMMIT = 1 - OBJ_TREE = 2 - OBJ_BLOB = 3 - OBJ_TAG = 4 +module Git + module Raw + module Internal + OBJ_NONE = 0 + OBJ_COMMIT = 1 + OBJ_TREE = 2 + OBJ_BLOB = 3 + OBJ_TAG = 4 - OBJ_TYPES = [nil, :commit, :tree, :blob, :tag].freeze + OBJ_TYPES = [nil, :commit, :tree, :blob, :tag].freeze - class RawObject - attr_accessor :type, :content - def initialize(type, content) - @type = type - @content = content - end + class RawObject + attr_accessor :type, :content + def initialize(type, content) + @type = type + @content = content + end - def sha1 - Digest::SHA1.digest("%s %d\0" % [@type, @content.length] + @content) - end + def sha1 + Digest::SHA1.digest("%s %d\0" % [@type, @content.length] + @content) + end + end + end end -end end +end diff --git a/lib/git/raw/internal/pack.rb b/lib/git/raw/internal/pack.rb index edfeada..6980a98 100644 --- a/lib/git/raw/internal/pack.rb +++ b/lib/git/raw/internal/pack.rb @@ -2,232 +2,240 @@ require 'zlib' require 'git/raw/internal/object' require 'git/raw/internal/mmap' -module Git module Raw module Internal - class PackFormatError < StandardError - end - - class PackStorage - OBJ_OFS_DELTA = 6 - OBJ_REF_DELTA = 7 - - FanOutCount = 256 - SHA1Size = 20 - IdxOffsetSize = 4 - OffsetSize = 4 - OffsetStart = FanOutCount * IdxOffsetSize - SHA1Start = OffsetStart + OffsetSize - EntrySize = OffsetSize + SHA1Size - - def initialize(file) - if file =~ /\.idx$/ - file = file[0...-3] + 'pack' +module Git + module Raw + module Internal + class PackFormatError < StandardError end - @name = file - @packfile = File.open(file) - @idxfile = File.open(file[0...-4]+'idx') - @idx = Mmap.new(@idxfile) - - @offsets = [0] - FanOutCount.times do |i| - pos = @idx[i * IdxOffsetSize,IdxOffsetSize].unpack('N')[0] - if pos < @offsets[i] - raise PackFormatError, "pack #@name has discontinuous index #{i}" + class PackStorage + OBJ_OFS_DELTA = 6 + OBJ_REF_DELTA = 7 + + FanOutCount = 256 + SHA1Size = 20 + IdxOffsetSize = 4 + OffsetSize = 4 + OffsetStart = FanOutCount * IdxOffsetSize + SHA1Start = OffsetStart + OffsetSize + EntrySize = OffsetSize + SHA1Size + + def initialize(file) + if file =~ /\.idx$/ + file = file[0...-3] + 'pack' + end + + @name = file + @packfile = File.open(file) + @idxfile = File.open(file[0...-4]+'idx') + @idx = Mmap.new(@idxfile) + + @offsets = [0] + FanOutCount.times do |i| + pos = @idx[i * IdxOffsetSize,IdxOffsetSize].unpack('N')[0] + if pos < @offsets[i] + raise PackFormatError, "pack #@name has discontinuous index #{i}" + end + @offsets << pos + end + + @size = @offsets[-1] end - @offsets << pos - end - - @size = @offsets[-1] - end - def close - @packfile.close - @idx.unmap - @idxfile.close - end - - def [](sha1) - offset = find_object(sha1) - return nil if !offset - return parse_object(offset) - end - - def each_entry - pos = OffsetStart - @size.times do - offset = @idx[pos,OffsetSize].unpack('N')[0] - sha1 = @idx[pos+OffsetSize,SHA1Size] - pos += EntrySize - yield sha1, offset - end - end - - def each_sha1 - # unpacking the offset is quite expensive, so - # we avoid using #each - pos = SHA1Start - @size.times do - sha1 = @idx[pos,SHA1Size] - pos += EntrySize - yield sha1 - end - end - - def find_object(sha1) - slot = sha1[0] - first, last = @offsets[slot,2] - while first < last - mid = (first + last) / 2 - midsha1 = @idx[SHA1Start + mid * EntrySize,SHA1Size] - cmp = midsha1 <=> sha1 - - if cmp < 0 - first = mid + 1 - elsif cmp > 0 - last = mid - else - pos = OffsetStart + mid * EntrySize - offset = @idx[pos,OffsetSize].unpack('N')[0] - return offset + def name + @name + end + + def close + @packfile.close + @idx.unmap + @idxfile.close end - end - - nil - end - private :find_object - def parse_object(offset) - data, type = unpack_object(offset) - RawObject.new(OBJ_TYPES[type], data) - end - protected :parse_object - - def unpack_object(offset) - obj_offset = offset - @packfile.seek(offset) - - c = @packfile.read(1)[0] - size = c & 0xf - type = (c >> 4) & 7 - shift = 4 - offset += 1 - while c & 0x80 != 0 - c = @packfile.read(1)[0] - size |= ((c & 0x7f) << shift) - shift += 7 - offset += 1 - end + def [](sha1) + offset = find_object(sha1) + return nil if !offset + return parse_object(offset) + end - case type - when OBJ_OFS_DELTA, OBJ_REF_DELTA - data, type = unpack_deltified(type, offset, obj_offset, size) - when OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG - data = unpack_compressed(offset, size) - else - raise PackFormatError, "invalid type #{type}" - end - [data, type] - end - private :unpack_object - - def unpack_deltified(type, offset, obj_offset, size) - @packfile.seek(offset) - data = @packfile.read(SHA1Size) - - if type == OBJ_OFS_DELTA - i = 0 - c = data[i] - base_offset = c & 0x7f - while c & 0x80 != 0 - c = data[i += 1] - base_offset += 1 - base_offset <<= 7 - base_offset |= c & 0x7f + def each_entry + pos = OffsetStart + @size.times do + offset = @idx[pos,OffsetSize].unpack('N')[0] + sha1 = @idx[pos+OffsetSize,SHA1Size] + pos += EntrySize + yield sha1, offset + end end - base_offset = obj_offset - base_offset - offset += i + 1 - else - base_offset = find_object(data) - offset += SHA1Size - end - base, type = unpack_object(base_offset) - delta = unpack_compressed(offset, size) - [patch_delta(base, delta), type] - end - private :unpack_deltified - - def unpack_compressed(offset, destsize) - outdata = "" - @packfile.seek(offset) - zstr = Zlib::Inflate.new - while outdata.size < destsize - indata = @packfile.read(4096) - if indata.size == 0 - raise PackFormatError, 'error reading pack data' + def each_sha1 + # unpacking the offset is quite expensive, so + # we avoid using #each + pos = SHA1Start + @size.times do + sha1 = @idx[pos,SHA1Size] + pos += EntrySize + yield sha1 + end end - outdata += zstr.inflate(indata) - end - if outdata.size > destsize - raise PackFormatError, 'error reading pack data' - end - zstr.close - outdata - end - private :unpack_compressed - def patch_delta(base, delta) - src_size, pos = patch_delta_header_size(delta, 0) - if src_size != base.size - raise PackFormatError, 'invalid delta data' - end + def find_object(sha1) + slot = sha1[0] + first, last = @offsets[slot,2] + while first < last + mid = (first + last) / 2 + midsha1 = @idx[SHA1Start + mid * EntrySize,SHA1Size] + cmp = midsha1 <=> sha1 + + if cmp < 0 + first = mid + 1 + elsif cmp > 0 + last = mid + else + pos = OffsetStart + mid * EntrySize + offset = @idx[pos,OffsetSize].unpack('N')[0] + return offset + end + end + + nil + end + private :find_object - dest_size, pos = patch_delta_header_size(delta, pos) - dest = "" - while pos < delta.size - c = delta[pos] - pos += 1 - if c & 0x80 != 0 - pos -= 1 - cp_off = cp_size = 0 - cp_off = delta[pos += 1] if c & 0x01 != 0 - cp_off |= delta[pos += 1] << 8 if c & 0x02 != 0 - cp_off |= delta[pos += 1] << 16 if c & 0x04 != 0 - cp_off |= delta[pos += 1] << 24 if c & 0x08 != 0 - cp_size = delta[pos += 1] if c & 0x10 != 0 - cp_size |= delta[pos += 1] << 8 if c & 0x20 != 0 - cp_size |= delta[pos += 1] << 16 if c & 0x40 != 0 - cp_size = 0x10000 if cp_size == 0 - pos += 1 - dest += base[cp_off,cp_size] - elsif c != 0 - dest += delta[pos,c] - pos += c - else - raise PackFormatError, 'invalid delta data' + def parse_object(offset) + data, type = unpack_object(offset) + RawObject.new(OBJ_TYPES[type], data) end - end - dest - end - private :patch_delta - - def patch_delta_header_size(delta, pos) - size = 0 - shift = 0 - begin - c = delta[pos] - if c == nil - raise PackFormatError, 'invalid delta header' + protected :parse_object + + def unpack_object(offset) + obj_offset = offset + @packfile.seek(offset) + + c = @packfile.read(1)[0] + size = c & 0xf + type = (c >> 4) & 7 + shift = 4 + offset += 1 + while c & 0x80 != 0 + c = @packfile.read(1)[0] + size |= ((c & 0x7f) << shift) + shift += 7 + offset += 1 + end + + case type + when OBJ_OFS_DELTA, OBJ_REF_DELTA + data, type = unpack_deltified(type, offset, obj_offset, size) + when OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG + data = unpack_compressed(offset, size) + else + raise PackFormatError, "invalid type #{type}" + end + [data, type] + end + private :unpack_object + + def unpack_deltified(type, offset, obj_offset, size) + @packfile.seek(offset) + data = @packfile.read(SHA1Size) + + if type == OBJ_OFS_DELTA + i = 0 + c = data[i] + base_offset = c & 0x7f + while c & 0x80 != 0 + c = data[i += 1] + base_offset += 1 + base_offset <<= 7 + base_offset |= c & 0x7f + end + base_offset = obj_offset - base_offset + offset += i + 1 + else + base_offset = find_object(data) + offset += SHA1Size + end + + base, type = unpack_object(base_offset) + delta = unpack_compressed(offset, size) + [patch_delta(base, delta), type] + end + private :unpack_deltified + + def unpack_compressed(offset, destsize) + outdata = "" + @packfile.seek(offset) + zstr = Zlib::Inflate.new + while outdata.size < destsize + indata = @packfile.read(4096) + if indata.size == 0 + raise PackFormatError, 'error reading pack data' + end + outdata += zstr.inflate(indata) + end + if outdata.size > destsize + raise PackFormatError, 'error reading pack data' + end + zstr.close + outdata end - pos += 1 - size |= (c & 0x7f) << shift - shift += 7 - end while c & 0x80 != 0 - [size, pos] + private :unpack_compressed + + def patch_delta(base, delta) + src_size, pos = patch_delta_header_size(delta, 0) + if src_size != base.size + raise PackFormatError, 'invalid delta data' + end + + dest_size, pos = patch_delta_header_size(delta, pos) + dest = "" + while pos < delta.size + c = delta[pos] + pos += 1 + if c & 0x80 != 0 + pos -= 1 + cp_off = cp_size = 0 + cp_off = delta[pos += 1] if c & 0x01 != 0 + cp_off |= delta[pos += 1] << 8 if c & 0x02 != 0 + cp_off |= delta[pos += 1] << 16 if c & 0x04 != 0 + cp_off |= delta[pos += 1] << 24 if c & 0x08 != 0 + cp_size = delta[pos += 1] if c & 0x10 != 0 + cp_size |= delta[pos += 1] << 8 if c & 0x20 != 0 + cp_size |= delta[pos += 1] << 16 if c & 0x40 != 0 + cp_size = 0x10000 if cp_size == 0 + pos += 1 + dest += base[cp_off,cp_size] + elsif c != 0 + dest += delta[pos,c] + pos += c + else + raise PackFormatError, 'invalid delta data' + end + end + dest + end + private :patch_delta + + def patch_delta_header_size(delta, pos) + size = 0 + shift = 0 + begin + c = delta[pos] + if c == nil + raise PackFormatError, 'invalid delta header' + end + pos += 1 + size |= (c & 0x7f) << shift + shift += 7 + end while c & 0x80 != 0 + [size, pos] + end + private :patch_delta_header_size + end end - private :patch_delta_header_size - end -end end + end +end if $0 == __FILE__ ARGV.each do |path| diff --git a/lib/git/raw/object.rb b/lib/git/raw/object.rb index 7e3e618..f10d853 100644 --- a/lib/git/raw/object.rb +++ b/lib/git/raw/object.rb @@ -263,6 +263,6 @@ module Git :tag end end - + end end \ No newline at end of file diff --git a/tests/units/test_raw_internals.rb b/tests/units/test_raw_internals.rb index b135e52..4299a2b 100644 --- a/tests/units/test_raw_internals.rb +++ b/tests/units/test_raw_internals.rb @@ -6,10 +6,30 @@ class TestRawInternals < Test::Unit::TestCase def setup set_file_paths - @git = Git.open(@wdir) end def test_raw_log + g = Git.bare(@wbare) + #g.repack + + c = g.object("HEAD") + puts sha = c.sha + + repo = Git::Raw::Repository.new(@wbare) + while sha do + o = repo.get_raw_object_by_sha1(sha) + c = Git::Raw::Object.from_raw(o) + + sha = c.parent.first + puts sha + end + + g.log(60).each do |c| + puts c.sha + end + + puts c.inspect + end end \ No newline at end of file -- cgit From f1366b39891402b0db9de661ad181089bfd79053 Mon Sep 17 00:00:00 2001 From: scott Chacon Date: Fri, 23 Nov 2007 11:16:46 -0800 Subject: got log and cat-file moved to pure ruby --- camping/gitweb.rb | 13 ++--- lib/git/lib.rb | 27 ++++++++-- lib/git/raw/repository.rb | 109 ++++++++++++++++++++++++++++++++++++++ tests/units/test_raw_internals.rb | 41 +++++++------- 4 files changed, 162 insertions(+), 28 deletions(-) create mode 100644 lib/git/raw/repository.rb diff --git a/camping/gitweb.rb b/camping/gitweb.rb index 092649d..8dc40ba 100644 --- a/camping/gitweb.rb +++ b/camping/gitweb.rb @@ -13,6 +13,7 @@ require 'lib/git' # todo # - diff/patch between any two objects # - expand patch to entire file +# - set title properly # - grep / search function # - prettify : http://projects.wh.techno-weenie.net/changesets/3030 # - add user model (add/remove repos) @@ -90,10 +91,7 @@ module GitWeb::Controllers class View < R '/view/(\d+)' def get repo_id @repo = Repository.find repo_id - logger = Logger.new('/tmp/git.log') - logger.level = Logger::INFO - - @git = Git.bare(@repo.path, :log => logger) + @git = Git.bare(@repo.path) render :view end end @@ -109,7 +107,10 @@ module GitWeb::Controllers class Commit < R '/commit/(\d+)/(\w+)' def get repo_id, sha @repo = Repository.find repo_id - @git = Git.bare(@repo.path) + logger = Logger.new('/tmp/git.log') + logger.level = Logger::INFO + + @git = Git.bare(@repo.path, :log => logger) @commit = @git.gcommit(sha) render :commit end @@ -212,7 +213,7 @@ module GitWeb::Views body :onload => "sh_highlightDocument();" do before = Time.now().usec self << yield - self << ((Time.now().usec - before).to_f / 60).to_s + ' sec' + self << '
' + ((Time.now().usec - before).to_f / 60).to_s + ' sec' end end end diff --git a/lib/git/lib.rb b/lib/git/lib.rb index eb06875..9c6a041 100644 --- a/lib/git/lib.rb +++ b/lib/git/lib.rb @@ -13,6 +13,7 @@ module Git @path = nil @logger = nil + @raw_repo = nil def initialize(base = nil, logger = nil) if base.is_a?(Git::Base) @@ -75,6 +76,15 @@ module Git end def full_log_commits(opts = {}) + if !(opts[:since] || opts[:between] || opts[:path_limiter]) + # can do this in pure ruby + sha = revparse(opts[:object] || branch_current || 'master') + count = opts[:count] || 30 + + repo = Git::Raw::Repository.new(@git_dir) + return process_commit_data(repo.log(sha, count)) + end + arr_opts = ['--pretty=raw'] arr_opts << "-#{opts[:count]}" if opts[:count] arr_opts << "--since=\"#{opts[:since]}\"" if opts[:since].is_a? String @@ -92,10 +102,13 @@ module Git end head = File.join(@git_dir, 'refs', 'heads', string) - return File.read(head) if File.file?(head) + return File.read(head).chomp if File.file?(head) head = File.join(@git_dir, 'refs', 'remotes', string) - return File.read(head) if File.file?(head) + return File.read(head).chomp if File.file?(head) + + head = File.join(@git_dir, 'refs', 'tags', string) + return File.read(head).chomp if File.file?(head) command('rev-parse', string) end @@ -111,17 +124,22 @@ module Git def object_size(sha) command('cat-file', ['-s', sha]).to_i end + + def get_raw_repo + @raw_repo ||= Git::Raw::Repository.new(@git_dir) + end # returns useful array of raw commit object data def commit_data(sha) sha = sha.to_s - cdata = command_lines('cat-file', ['commit', sha]) + cdata = get_raw_repo.cat_file(revparse(sha)) + #cdata = command_lines('cat-file', ['commit', sha]) process_commit_data(cdata, sha) end def process_commit_data(data, sha = nil) in_message = false - + if sha hsh = {'sha' => sha, 'message' => '', 'parent' => []} else @@ -129,6 +147,7 @@ module Git end data.each do |line| + line = line.chomp if in_message && line != '' hsh['message'] += line + "\n" end diff --git a/lib/git/raw/repository.rb b/lib/git/raw/repository.rb new file mode 100644 index 0000000..4a1c897 --- /dev/null +++ b/lib/git/raw/repository.rb @@ -0,0 +1,109 @@ +require 'git/raw/internal/object' +require 'git/raw/internal/pack' +require 'git/raw/internal/loose' +require 'git/raw/object' + +module Git + module Raw + + class Repository + def initialize(git_dir) + @git_dir = git_dir + @loose = Raw::Internal::LooseStorage.new(git_path("objects")) + @packs = [] + initpacks + end + + def show + @packs.each do |p| + puts p.name + puts + p.each_sha1 do |s| + puts "**#{p[s].type}**" + if p[s].type.to_s == 'commit' + puts s.unpack('H*') + puts p[s].content + end + end + puts + end + end + + def cat_file(sha) + get_raw_object_by_sha1(sha).content rescue nil + end + + def log(sha, count = 30) + output = '' + i = 0 + + while sha && (i < count) do + o = get_raw_object_by_sha1(sha) + c = Git::Raw::Object.from_raw(o) + + output += "commit #{sha}\n" + output += o.content + "\n" + + sha = c.parent.first + i += 1 + end + + output + end + + def get_object_by_sha1(sha1) + r = get_raw_object_by_sha1(sha1) + return nil if !r + Object.from_raw(r, self) + end + + def get_raw_object_by_sha1(sha1) + sha1 = [sha1].pack("H*") + + # try packs + @packs.each do |pack| + o = pack[sha1] + return o if o + end + + # try loose storage + o = @loose[sha1] + return o if o + + # try packs again, maybe the object got packed in the meantime + initpacks + @packs.each do |pack| + o = pack[sha1] + return o if o + end + + nil + end + + protected + + def git_path(path) + return "#@git_dir/#{path}" + end + + private + + def initpacks + @packs.each do |pack| + pack.close + end + @packs = [] + Dir.open(git_path("objects/pack/")) do |dir| + dir.each do |entry| + if entry =~ /\.pack$/i + @packs << Git::Raw::Internal::PackStorage.new(git_path("objects/pack/" \ + + entry)) + end + end + end + end + + end + + end +end diff --git a/tests/units/test_raw_internals.rb b/tests/units/test_raw_internals.rb index 4299a2b..1437845 100644 --- a/tests/units/test_raw_internals.rb +++ b/tests/units/test_raw_internals.rb @@ -1,5 +1,5 @@ #!/usr/bin/env ruby - +require 'logger' require File.dirname(__FILE__) + '/../test_helper' class TestRawInternals < Test::Unit::TestCase @@ -10,26 +10,31 @@ class TestRawInternals < Test::Unit::TestCase def test_raw_log g = Git.bare(@wbare) - #g.repack + t_log(g) + end + + def test_packed_log + g = Git.bare(@wbare) + g.repack + t_log(g) + end + + def test_commit_object + g = Git.bare(@wbare, :log => Logger.new(STDOUT)) - c = g.object("HEAD") - puts sha = c.sha + c = g.gcommit("v2.5") + assert_equal('test', c.message) + end + + def t_log(g) + c = g.object("v2.5") + sha = c.sha repo = Git::Raw::Repository.new(@wbare) - while sha do - o = repo.get_raw_object_by_sha1(sha) - c = Git::Raw::Object.from_raw(o) - - sha = c.parent.first - puts sha - end - - g.log(60).each do |c| - puts c.sha - end - - puts c.inspect + raw_out = repo.log(sha) + assert_equal('commit 546bec6f8872efa41d5d97a369f669165ecda0de', raw_out.split("\n").first) + assert_equal('546bec6f8872efa41d5d97a369f669165ecda0de', c.log(30).first.sha) end - + end \ No newline at end of file -- cgit From 3fddf300bd33b356540bee50ae17590ea9b61341 Mon Sep 17 00:00:00 2001 From: scott Chacon Date: Fri, 23 Nov 2007 11:50:02 -0800 Subject: started the ruby-only command line git client --- bin/gitr | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/git/base.rb | 4 ++++ lib/git/branches.rb | 12 ++++++++++ lib/git/lib.rb | 5 ++-- 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100755 bin/gitr diff --git a/bin/gitr b/bin/gitr new file mode 100755 index 0000000..53c8055 --- /dev/null +++ b/bin/gitr @@ -0,0 +1,69 @@ +#!/usr/bin/env ruby + +# This is a command line client that can do a number of read operations +# on a git repository in pure ruby. This may be helpful if you have access +# to a computer that has no C compiler but you want to do some git stuff +# on it. It's also helpful for me to test Git stuff with. +# +# author : Scott Chacon (schacon@gmail.com) +# +# todo: +# add --git-dir +# add --log-file +# add --help + +#require 'lib/git' +require 'rubygems' +require 'git' +require 'logger' + +command = ARGV[0] + +if !command + puts 'You have to provide a command' + puts 'usage: gitr (command) [args]' + puts + puts 'commands: log' + puts ' log-shas' + puts ' cat-file' + puts ' rev-parse' + puts ' branches' + puts ' config' + exit +end + +git_dir = ENV['GIT_DIR'] || '.git' +@git = Git.bare(git_dir, :log => Logger.new(STDOUT)) + +case command +when 'log' + # gitr log + @git.log.each do |l| + puts 'commit ' + l.sha + puts l.contents + puts + end +when 'log-shas' + # gitr log-shas + puts @git.log +when 'cat-file' + # gitr cat-file + puts @git.cat_file(ARGV[1]) +when 'rev-parse' + # gitr rev-parse + puts @git.revparse(ARGV[1]) +when 'branches' + # gitr branches + puts @git.branches +when 'config' + # gitr config + @git.config.sort.each do |k,v| + puts "#{k} : #{v}" + end +end + +# gitr ls-tree +# gitr pack-browse + +# gitr diff / stats ? +# output in yaml? \ No newline at end of file diff --git a/lib/git/base.rb b/lib/git/base.rb index 1820ee2..31ba9ff 100644 --- a/lib/git/base.rb +++ b/lib/git/base.rb @@ -429,6 +429,10 @@ module Git self.lib.revparse(objectish) end + def cat_file(objectish) + self.lib.object_contents(objectish) + end + # returns the name of the branch the working directory is currently on def current_branch self.lib.branch_current diff --git a/lib/git/branches.rb b/lib/git/branches.rb index 47d001a..60ab16b 100644 --- a/lib/git/branches.rb +++ b/lib/git/branches.rb @@ -41,5 +41,17 @@ module Git @branches[symbol.to_s] end + def to_s + out = '' + @branches.each do |k, b| + if b.current + out += "* " + b.to_s + "\n" + else + out += " " + b.to_s + "\n" + end + end + out + end + end end \ No newline at end of file diff --git a/lib/git/lib.rb b/lib/git/lib.rb index 9c6a041..120d9ce 100644 --- a/lib/git/lib.rb +++ b/lib/git/lib.rb @@ -81,7 +81,7 @@ module Git sha = revparse(opts[:object] || branch_current || 'master') count = opts[:count] || 30 - repo = Git::Raw::Repository.new(@git_dir) + repo = get_raw_repo return process_commit_data(repo.log(sha, count)) end @@ -182,7 +182,8 @@ module Git end def object_contents(sha) - command('cat-file', ['-p', sha]) + #command('cat-file', ['-p', sha]) + get_raw_repo.cat_file(revparse(sha)) end def ls_tree(sha) -- cgit From 07ebb951368ed31bdaebc2e820c62ced22c8bbe4 Mon Sep 17 00:00:00 2001 From: scott Chacon Date: Tue, 27 Nov 2007 08:06:51 -0800 Subject: added Matthias and Simon to credits for the gitrb code fixed an issue with raw object tree formatting added ls_tree implementation in raw git --- camping/gitweb.rb | 10 +++++----- lib/git/lib.rb | 16 +++++++++++----- lib/git/raw/internal/loose.rb | 10 ++++++++++ lib/git/raw/internal/mmap.rb | 10 ++++++++++ lib/git/raw/internal/object.rb | 10 ++++++++++ lib/git/raw/internal/pack.rb | 10 ++++++++++ lib/git/raw/object.rb | 28 ++++++++++++++++++++++++++-- lib/git/raw/repository.rb | 19 +++++++++++++++++-- tests/test_helper.rb | 10 ++++++++++ tests/units/test_index_ops.rb | 2 +- tests/units/test_lib.rb | 9 +++++++++ tests/units/test_raw_internals.rb | 26 ++++++++++++++++++++------ 12 files changed, 139 insertions(+), 21 deletions(-) diff --git a/camping/gitweb.rb b/camping/gitweb.rb index 8dc40ba..3d1b5d6 100644 --- a/camping/gitweb.rb +++ b/camping/gitweb.rb @@ -107,10 +107,7 @@ module GitWeb::Controllers class Commit < R '/commit/(\d+)/(\w+)' def get repo_id, sha @repo = Repository.find repo_id - logger = Logger.new('/tmp/git.log') - logger.level = Logger::INFO - - @git = Git.bare(@repo.path, :log => logger) + @git = Git.bare(@repo.path) @commit = @git.gcommit(sha) render :commit end @@ -119,7 +116,10 @@ module GitWeb::Controllers class Tree < R '/tree/(\d+)/(\w+)' def get repo_id, sha @repo = Repository.find repo_id - @git = Git.bare(@repo.path) + logger = Logger.new('/tmp/git.log') + logger.level = Logger::INFO + + @git = Git.bare(@repo.path, :log => logger) @tree = @git.gtree(sha) render :tree end diff --git a/lib/git/lib.rb b/lib/git/lib.rb index 120d9ce..decd6d4 100644 --- a/lib/git/lib.rb +++ b/lib/git/lib.rb @@ -183,16 +183,22 @@ module Git def object_contents(sha) #command('cat-file', ['-p', sha]) - get_raw_repo.cat_file(revparse(sha)) + get_raw_repo.cat_file(revparse(sha)).chomp end def ls_tree(sha) data = {'blob' => {}, 'tree' => {}} - command_lines('ls-tree', sha.to_s).each do |line| - (info, filenm) = line.split("\t") - (mode, type, sha) = info.split - data[type][filenm] = {:mode => mode, :sha => sha} + + get_raw_repo.object(revparse(sha)).entry.each do |e| + data[e.format_type][e.name] = {:mode => e.format_mode, :sha => e.sha1} end + + #command_lines('ls-tree', sha.to_s).each do |line| + # (info, filenm) = line.split("\t") + # (mode, type, sha) = info.split + # data[type][filenm] = {:mode => mode, :sha => sha} + #end + data end diff --git a/lib/git/raw/internal/loose.rb b/lib/git/raw/internal/loose.rb index d8ec6fb..53d4334 100644 --- a/lib/git/raw/internal/loose.rb +++ b/lib/git/raw/internal/loose.rb @@ -1,3 +1,13 @@ +# +# converted from the gitrb project +# +# authors: +# Matthias Lederhofer +# Simon 'corecode' Schubert +# +# provides native ruby access to git objects and pack files +# + require 'zlib' require 'digest/sha1' diff --git a/lib/git/raw/internal/mmap.rb b/lib/git/raw/internal/mmap.rb index 15b5628..78de164 100644 --- a/lib/git/raw/internal/mmap.rb +++ b/lib/git/raw/internal/mmap.rb @@ -1,3 +1,13 @@ +# +# converted from the gitrb project +# +# authors: +# Matthias Lederhofer +# Simon 'corecode' Schubert +# +# provides native ruby access to git objects and pack files +# + begin require 'mmap' rescue LoadError diff --git a/lib/git/raw/internal/object.rb b/lib/git/raw/internal/object.rb index 7f95685..172b917 100644 --- a/lib/git/raw/internal/object.rb +++ b/lib/git/raw/internal/object.rb @@ -1,3 +1,13 @@ +# +# converted from the gitrb project +# +# authors: +# Matthias Lederhofer +# Simon 'corecode' Schubert +# +# provides native ruby access to git objects and pack files +# + require 'digest/sha1' module Git diff --git a/lib/git/raw/internal/pack.rb b/lib/git/raw/internal/pack.rb index 6980a98..8d5141e 100644 --- a/lib/git/raw/internal/pack.rb +++ b/lib/git/raw/internal/pack.rb @@ -1,3 +1,13 @@ +# +# converted from the gitrb project +# +# authors: +# Matthias Lederhofer +# Simon 'corecode' Schubert +# +# provides native ruby access to git objects and pack files +# + require 'zlib' require 'git/raw/internal/object' require 'git/raw/internal/mmap' diff --git a/lib/git/raw/object.rb b/lib/git/raw/object.rb index f10d853..5c3969d 100644 --- a/lib/git/raw/object.rb +++ b/lib/git/raw/object.rb @@ -1,3 +1,13 @@ +# +# converted from the gitrb project +# +# authors: +# Matthias Lederhofer +# Simon 'corecode' Schubert +# +# provides native ruby access to git objects and pack files +# + require 'digest/sha1' module Git @@ -132,6 +142,21 @@ module Git end end + def format_type + case type + when :link + 'link' + when :directory + 'tree' + when :file + 'blob' + end + end + + def format_mode + "%06o" % @mode + end + def raw "%o %s\0%s" % [@mode, @name, [@sha1].pack("H*")] end @@ -160,8 +185,7 @@ module Git def raw_content # TODO: sort correctly #@entry.sort { |a,b| a.name <=> b.name }. - @entry. - collect { |e| e.raw }.join + @entry.collect { |e| [[e.format_mode, e.format_type, e.sha1].join(' '), e.name].join("\t") }.join("\n") end end diff --git a/lib/git/raw/repository.rb b/lib/git/raw/repository.rb index 4a1c897..bd5e971 100644 --- a/lib/git/raw/repository.rb +++ b/lib/git/raw/repository.rb @@ -1,3 +1,13 @@ +# +# converted from the gitrb project +# +# authors: +# Matthias Lederhofer +# Simon 'corecode' Schubert +# +# provides native ruby access to git objects and pack files +# + require 'git/raw/internal/object' require 'git/raw/internal/pack' require 'git/raw/internal/loose' @@ -28,9 +38,14 @@ module Git puts end end - + + def object(sha) + o = get_raw_object_by_sha1(sha) + c = Git::Raw::Object.from_raw(o) + end + def cat_file(sha) - get_raw_object_by_sha1(sha).content rescue nil + object(sha).raw_content end def log(sha, count = 30) diff --git a/tests/test_helper.rb b/tests/test_helper.rb index 9907ffe..e40174a 100644 --- a/tests/test_helper.rb +++ b/tests/test_helper.rb @@ -28,6 +28,16 @@ class Test::Unit::TestCase end end + + def with_temp_bare + in_temp_dir do |path| + g = Git.clone(@wbare, 'new') + Dir.chdir('new') do + yield g + end + end + end + def create_temp_repo(clone_path) filename = 'git_test' + Time.now.to_i.to_s + rand(300).to_s.rjust(3, '0') @tmp_path = File.join("/tmp/", filename) diff --git a/tests/units/test_index_ops.rb b/tests/units/test_index_ops.rb index f92ae9e..dcd8498 100644 --- a/tests/units/test_index_ops.rb +++ b/tests/units/test_index_ops.rb @@ -10,7 +10,7 @@ class TestIndexOps < Test::Unit::TestCase end def test_add - in_temp_dir(false) do |path| + in_temp_dir do |path| g = Git.clone(@wbare, 'new') Dir.chdir('new') do assert_equal('100644', g.status['example.txt'].mode_index) diff --git a/tests/units/test_lib.rb b/tests/units/test_lib.rb index f9170e6..7cbbeef 100644 --- a/tests/units/test_lib.rb +++ b/tests/units/test_lib.rb @@ -102,6 +102,15 @@ class TestLib < Test::Unit::TestCase assert_equal('+refs/heads/*:refs/remotes/working/*', config['fetch']) end + + def test_ls_tree + tree = @lib.ls_tree('94c827875e2cadb8bc8d4cdd900f19aa9e8634c7') + assert_equal("3aac4b445017a8fc07502670ec2dbf744213dd48", tree['blob']['example.txt'][:sha]) + assert_equal("100644", tree['blob']['example.txt'][:mode]) + assert(tree['tree']) + end + + # options this will accept # :treeish # :path_limiter diff --git a/tests/units/test_raw_internals.rb b/tests/units/test_raw_internals.rb index 1437845..03a7916 100644 --- a/tests/units/test_raw_internals.rb +++ b/tests/units/test_raw_internals.rb @@ -9,14 +9,16 @@ class TestRawInternals < Test::Unit::TestCase end def test_raw_log - g = Git.bare(@wbare) - t_log(g) + with_temp_bare do |g| + t_log(g) + end end def test_packed_log - g = Git.bare(@wbare) - g.repack - t_log(g) + with_temp_bare do |g| + g.repack + t_log(g) + end end def test_commit_object @@ -26,15 +28,27 @@ class TestRawInternals < Test::Unit::TestCase assert_equal('test', c.message) end + def test_lstree + g = Git.bare(@wbare) + c = g.object("v2.5").gtree + sha = c.sha + + repo = Git::Raw::Repository.new(@wbare) + puts repo.object(sha).inspect + end + def t_log(g) c = g.object("v2.5") sha = c.sha - repo = Git::Raw::Repository.new(@wbare) + repo = Git::Raw::Repository.new(g.repo.path) raw_out = repo.log(sha) assert_equal('commit 546bec6f8872efa41d5d97a369f669165ecda0de', raw_out.split("\n").first) assert_equal('546bec6f8872efa41d5d97a369f669165ecda0de', c.log(30).first.sha) end + + + end \ No newline at end of file -- cgit From 6a9db968e8563bc27b8f56f9d413159a2e14cf67 Mon Sep 17 00:00:00 2001 From: scott Chacon Date: Tue, 27 Nov 2007 08:21:28 -0800 Subject: fixed issue with running a 'git log' with an object that won't rev-parse (file) --- camping/gitweb.rb | 10 +++++----- lib/git/lib.rb | 6 ++++-- tests/units/test_log.rb | 3 ++- tests/units/test_raw_internals.rb | 5 ++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/camping/gitweb.rb b/camping/gitweb.rb index 3d1b5d6..29a4ab3 100644 --- a/camping/gitweb.rb +++ b/camping/gitweb.rb @@ -116,10 +116,7 @@ module GitWeb::Controllers class Tree < R '/tree/(\d+)/(\w+)' def get repo_id, sha @repo = Repository.find repo_id - logger = Logger.new('/tmp/git.log') - logger.level = Logger::INFO - - @git = Git.bare(@repo.path, :log => logger) + @git = Git.bare(@repo.path) @tree = @git.gtree(sha) render :tree end @@ -128,7 +125,10 @@ module GitWeb::Controllers class Blob < R '/blob/(\d+)/(.*?)/(\w+)' def get repo_id, file, sha @repo = Repository.find repo_id - @git = Git.bare(@repo.path) + logger = Logger.new('/tmp/git.log') + logger.level = Logger::INFO + + @git = Git.bare(@repo.path, :log => logger) @blob = @git.gblob(sha) @file = file render :blob diff --git a/lib/git/lib.rb b/lib/git/lib.rb index decd6d4..cd24fca 100644 --- a/lib/git/lib.rb +++ b/lib/git/lib.rb @@ -81,8 +81,10 @@ module Git sha = revparse(opts[:object] || branch_current || 'master') count = opts[:count] || 30 - repo = get_raw_repo - return process_commit_data(repo.log(sha, count)) + if /\w{40}/.match(sha) # valid sha + repo = get_raw_repo + return process_commit_data(repo.log(sha, count)) + end end arr_opts = ['--pretty=raw'] diff --git a/tests/units/test_log.rb b/tests/units/test_log.rb index 11dcb27..7bcd83b 100644 --- a/tests/units/test_log.rb +++ b/tests/units/test_log.rb @@ -1,10 +1,11 @@ #!/usr/bin/env ruby - +require 'logger' require File.dirname(__FILE__) + '/../test_helper' class TestLog < Test::Unit::TestCase def setup set_file_paths + #@git = Git.open(@wdir, :log => Logger.new(STDOUT)) @git = Git.open(@wdir) end diff --git a/tests/units/test_raw_internals.rb b/tests/units/test_raw_internals.rb index 03a7916..b37a66d 100644 --- a/tests/units/test_raw_internals.rb +++ b/tests/units/test_raw_internals.rb @@ -22,8 +22,7 @@ class TestRawInternals < Test::Unit::TestCase end def test_commit_object - g = Git.bare(@wbare, :log => Logger.new(STDOUT)) - + g = Git.bare(@wbare) c = g.gcommit("v2.5") assert_equal('test', c.message) end @@ -34,7 +33,7 @@ class TestRawInternals < Test::Unit::TestCase sha = c.sha repo = Git::Raw::Repository.new(@wbare) - puts repo.object(sha).inspect + assert_equal('ex_dir', repo.object(sha).entry.first.name) end def t_log(g) -- cgit