summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorscott Chacon <schacon@agadorsparticus.(none)>2007-11-27 08:21:48 -0800
committerscott Chacon <schacon@agadorsparticus.(none)>2007-11-27 08:21:48 -0800
commit779f21b307e7c119a56700fb14f88ba63a2cccc2 (patch)
treef3f03821dbe66f581ae2707e6e872395adc7afbe
parent55c3c134c5c19103ed52fbd615d5af351fa9bb34 (diff)
parent6a9db968e8563bc27b8f56f9d413159a2e14cf67 (diff)
downloadthird_party-ruby-git-779f21b307e7c119a56700fb14f88ba63a2cccc2.tar.gz
third_party-ruby-git-779f21b307e7c119a56700fb14f88ba63a2cccc2.tar.xz
third_party-ruby-git-779f21b307e7c119a56700fb14f88ba63a2cccc2.zip
Merge branches 'master' and 'internals'
-rwxr-xr-xbin/gitr69
-rw-r--r--camping/gitweb.rb17
-rw-r--r--lib/git.rb3
-rw-r--r--lib/git/base.rb4
-rw-r--r--lib/git/branches.rb12
-rw-r--r--lib/git/lib.rb46
-rw-r--r--lib/git/raw/internal/loose.rb110
-rw-r--r--lib/git/raw/internal/mmap.rb58
-rw-r--r--lib/git/raw/internal/object.rb37
-rw-r--r--lib/git/raw/internal/pack.rb258
-rw-r--r--lib/git/raw/object.rb292
-rw-r--r--lib/git/raw/repository.rb124
-rw-r--r--tests/test_helper.rb10
-rw-r--r--tests/units/test_index_ops.rb2
-rw-r--r--tests/units/test_lib.rb9
-rw-r--r--tests/units/test_log.rb3
-rw-r--r--tests/units/test_raw_internals.rb53
17 files changed, 1088 insertions, 19 deletions
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/camping/gitweb.rb b/camping/gitweb.rb
index 092649d..29a4ab3 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,7 @@ module GitWeb::Controllers
class Commit < R '/commit/(\d+)/(\w+)'
def get repo_id, sha
@repo = Repository.find repo_id
- @git = Git.bare(@repo.path)
+ @git = Git.bare(@repo.path)
@commit = @git.gcommit(sha)
render :commit
end
@@ -118,7 +116,7 @@ module GitWeb::Controllers
class Tree < R '/tree/(\d+)/(\w+)'
def get repo_id, sha
@repo = Repository.find repo_id
- @git = Git.bare(@repo.path)
+ @git = Git.bare(@repo.path)
@tree = @git.gtree(sha)
render :tree
end
@@ -127,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
@@ -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 << '<br/>' + ((Time.now().usec - before).to_f / 60).to_s + ' sec'
end
end
end
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/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 eb06875..cd24fca 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,17 @@ 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
+
+ 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']
arr_opts << "-#{opts[:count]}" if opts[:count]
arr_opts << "--since=\"#{opts[:since]}\"" if opts[:since].is_a? String
@@ -92,10 +104,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 +126,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 +149,7 @@ module Git
end
data.each do |line|
+ line = line.chomp
if in_message && line != ''
hsh['message'] += line + "\n"
end
@@ -163,16 +184,23 @@ module Git
end
def object_contents(sha)
- command('cat-file', ['-p', sha])
+ #command('cat-file', ['-p', 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
new file mode 100644
index 0000000..53d4334
--- /dev/null
+++ b/lib/git/raw/internal/loose.rb
@@ -0,0 +1,110 @@
+#
+# converted from the gitrb project
+#
+# authors:
+# Matthias Lederhofer <matled@gmx.net>
+# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
+#
+# provides native ruby access to git objects and pack files
+#
+
+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
+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..78de164
--- /dev/null
+++ b/lib/git/raw/internal/mmap.rb
@@ -0,0 +1,58 @@
+#
+# converted from the gitrb project
+#
+# authors:
+# Matthias Lederhofer <matled@gmx.net>
+# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
+#
+# provides native ruby access to git objects and pack files
+#
+
+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
+
+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..172b917
--- /dev/null
+++ b/lib/git/raw/internal/object.rb
@@ -0,0 +1,37 @@
+#
+# converted from the gitrb project
+#
+# authors:
+# Matthias Lederhofer <matled@gmx.net>
+# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
+#
+# provides native ruby access to git objects and pack files
+#
+
+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
+end
diff --git a/lib/git/raw/internal/pack.rb b/lib/git/raw/internal/pack.rb
new file mode 100644
index 0000000..8d5141e
--- /dev/null
+++ b/lib/git/raw/internal/pack.rb
@@ -0,0 +1,258 @@
+#
+# converted from the gitrb project
+#
+# authors:
+# Matthias Lederhofer <matled@gmx.net>
+# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
+#
+# provides native ruby access to git objects and pack files
+#
+
+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 name
+ @name
+ 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
+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..5c3969d
--- /dev/null
+++ b/lib/git/raw/object.rb
@@ -0,0 +1,292 @@
+#
+# converted from the gitrb project
+#
+# authors:
+# Matthias Lederhofer <matled@gmx.net>
+# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
+#
+# provides native ruby access to git objects and pack files
+#
+
+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 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
+ 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.format_mode, e.format_type, e.sha1].join(' '), e.name].join("\t") }.join("\n")
+ 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/lib/git/raw/repository.rb b/lib/git/raw/repository.rb
new file mode 100644
index 0000000..bd5e971
--- /dev/null
+++ b/lib/git/raw/repository.rb
@@ -0,0 +1,124 @@
+#
+# converted from the gitrb project
+#
+# authors:
+# Matthias Lederhofer <matled@gmx.net>
+# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
+#
+# 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'
+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 object(sha)
+ o = get_raw_object_by_sha1(sha)
+ c = Git::Raw::Object.from_raw(o)
+ end
+
+ def cat_file(sha)
+ object(sha).raw_content
+ 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/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_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
new file mode 100644
index 0000000..b37a66d
--- /dev/null
+++ b/tests/units/test_raw_internals.rb
@@ -0,0 +1,53 @@
+#!/usr/bin/env ruby
+require 'logger'
+require File.dirname(__FILE__) + '/../test_helper'
+
+class TestRawInternals < Test::Unit::TestCase
+
+ def setup
+ set_file_paths
+ end
+
+ def test_raw_log
+ with_temp_bare do |g|
+ t_log(g)
+ end
+ end
+
+ def test_packed_log
+ with_temp_bare do |g|
+ g.repack
+ t_log(g)
+ end
+ end
+
+ def test_commit_object
+ g = Git.bare(@wbare)
+ c = g.gcommit("v2.5")
+ 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)
+ assert_equal('ex_dir', repo.object(sha).entry.first.name)
+ end
+
+ def t_log(g)
+ c = g.object("v2.5")
+ sha = c.sha
+
+ 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