lib/gitrb/repository.rb in gitrb-0.0.8 vs lib/gitrb/repository.rb in gitrb-0.0.9
- old
+ new
@@ -1,35 +1,27 @@
-require 'zlib'
-require 'digest/sha1'
-require 'yaml'
-require 'fileutils'
-require 'logger'
-require 'enumerator'
-
-require 'gitrb/util'
-require 'gitrb/repository'
-require 'gitrb/object'
-require 'gitrb/blob'
-require 'gitrb/diff'
-require 'gitrb/tree'
-require 'gitrb/tag'
-require 'gitrb/user'
-require 'gitrb/pack'
-require 'gitrb/commit'
-require 'gitrb/trie'
-
module Gitrb
class NotFound < StandardError; end
- class Repository
- attr_reader :path, :index, :root, :branch, :lock_file, :head, :encoding
+ class CommandError < StandardError
+ attr_reader :command, :args, :output
- SHA_PATTERN = /[A-Fa-f0-9]{5,40}/
- REVISION_PATTERN = /[\w\-\.]+([\^~](\d+)?)*/
+ def initialize(command, args, output)
+ super("#{command} failed")
+ @command = command
+ @args = args
+ @output = output
+ end
+ end
- # Encoding stuff
+ class Repository
+ attr_reader :path, :root, :branch, :head, :encoding
+
+ SHA_PATTERN = /^[A-Fa-f0-9]{5,40}$/
+ REVISION_PATTERN = /^[\w\-\.]+([\^~](\d+)?)*$/
DEFAULT_ENCODING = 'utf-8'
+ MIN_GIT_VERSION = '1.6.0'
+ LOCK = 'Gitrb.Repository.lock'
if RUBY_VERSION > '1.9'
def set_encoding(s); s.force_encoding(@encoding); end
else
def set_encoding(s); s; end
@@ -44,21 +36,12 @@
@path = options[:path]
@path.chomp!('/')
@path += '/.git' if !@bare
- if options[:create] && !File.exists?("#{@path}/objects")
- FileUtils.mkpath(@path) if !File.exists?(@path)
- raise ArgumentError, "Not a valid Git repository: '#{@path}'" if !File.directory?(@path)
- if @bare
- Dir.chdir(@path) { git_init('--bare') }
- else
- Dir.chdir(@path[0..-6]) { git_init }
- end
- else
- raise ArgumentError, "Not a valid Git repository: '#{@path}'" if !File.directory?("#{@path}/objects")
- end
+ check_git_version if !options[:ignore_version]
+ open_repository(options[:create])
load_packs
load
end
@@ -73,31 +56,31 @@
load
end
# Has our repository been changed on disk?
def changed?
- head.nil? or head.id != read_head_id
+ head.nil? || head.id != read_head_id
end
# Load the repository, if it has been changed on disk.
def refresh
load if changed?
end
# Is there any transaction going on?
def in_transaction?
- Thread.current['gitrb_repository_lock']
+ Thread.current[LOCK]
end
# Diff
def diff(from, to, path = nil)
if from && !(Commit === from)
- raise ArgumentError if !(String === from)
+ raise ArgumentError, "Invalid sha: #{from}" if from !~ SHA_PATTERN
from = Reference.new(:repository => self, :id => from)
end
if !(Commit === to)
- raise ArgumentError if !(String === to)
+ raise ArgumentError, "Invalid sha: #{to}" if to !~ SHA_PATTERN
to = Reference.new(:repository => self, :id => to)
end
Diff.new(from, to, git_diff_tree('--root', '-u', '--full-index', from && from.id, to.id, '--', path))
end
@@ -129,11 +112,11 @@
committer ||= author
root.save
commit = Commit.new(:repository => self,
:tree => root,
- :parent => head,
+ :parents => head,
:author => author,
:committer => committer,
:message => message)
commit.save
@@ -143,21 +126,25 @@
commit
end
# Returns a list of commits starting from head commit.
def log(limit = 10, start = nil, path = nil)
- ### FIX: tformat need --pretty option
- args = ['--pretty=tformat:%H%n%P%n%T%n%an%n%ae%n%at%n%cn%n%ce%n%ct%n%x00%s%n%b%x00', "-#{limit}", ]
- args << start if start
- args << "--" << path if path && !path.empty?
+ limit = limit.to_s
+ start = start.to_s
+ raise ArgumentError, "Invalid limit: #{limit}" if limit !~ /^\d+$/
+ raise ArgumentError, "Invalid commit: #{start}" if start =~ /^\-/
+ args = ['--pretty=tformat:%H%n%P%n%T%n%an%n%ae%n%at%n%cn%n%ce%n%ct%n%x00%s%n%b%x00', "-#{limit}" ]
+ args << start if !start.empty?
+ args << '--' << path if path && !path.empty?
log = git_log(*args).split(/\n*\x00\n*/)
commits = []
log.each_slice(2) do |data, message|
data = data.split("\n")
+ parents = data[1].empty? ? nil : data[1].split(' ').map {|id| Reference.new(:repository => self, :id => id) }
commits << Commit.new(:repository => self,
:id => data[0],
- :parent => data[1].empty? ? nil : Reference.new(:repository => self, :id => data[1]),
+ :parents => parents,
:tree => Reference.new(:repository => self, :id => data[2]),
:author => User.new(data[3], data[4], Time.at(data[5].to_i)),
:committer => User.new(data[6], data[7], Time.at(data[8].to_i)),
:message => message.strip)
end
@@ -169,28 +156,30 @@
# Get an object by its id.
#
# Returns a tree, blob, commit or tag object.
def get(id)
- raise NotFound, "No id given" if id.nil?
+ raise ArgumentError, 'No id given' if !(String === id)
+
if id =~ SHA_PATTERN
- raise NotFound, "Sha too short" if id.length < 5
+ raise NotFound, "Sha too short: #{id}" if id.length < 5
list = @objects.find(id).to_a
+ raise NotFound, "Sha is ambiguous: #{id}" if list.size > 1
return list.first if list.size == 1
elsif id =~ REVISION_PATTERN
list = git_rev_parse(id).split("\n") rescue nil
- raise NotFound, "Revision not found" if !list || list.empty?
- raise NotFound, "Revision is ambiguous" if list.size > 1
+ raise NotFound, "Revision not found: #{id}" if !list || list.empty?
+ raise NotFound, "Revision is ambiguous: #{id}" if list.size > 1
id = list.first
end
@logger.debug "gitrb: Loading #{id}"
path = object_path(id)
if File.exists?(path) || (glob = Dir.glob(path + '*')).size >= 1
if glob
- raise NotFound, "Sha is ambiguous" if glob.size > 1
+ raise NotFound, "Sha is ambiguous: #{id}" if glob.size > 1
path = glob.first
id = path[-41..-40] + path[-38..-1]
end
buf = File.open(path, 'rb') { |f| f.read }
@@ -201,32 +190,32 @@
type, size = header.split(' ', 2)
raise NotFound, "Bad object: #{id}" if content.length != size.to_i
else
trie = @packs.find(id)
- raise NotFound, "Object not found" if !trie
+ raise NotFound, "Object not found: #{id}" if !trie
id += trie.key[-(41 - id.length)...-1]
list = trie.to_a
- raise NotFound, "Sha is ambiguous" if list.size > 1
+ raise NotFound, "Sha is ambiguous: #{id}" if list.size > 1
pack, offset = list.first
content, type = pack.get_object(offset)
end
@logger.debug "gitrb: Loaded #{id}"
set_encoding(id)
- object = Gitrb::Object.factory(type, :repository => self, :id => id, :data => content)
+ object = GitObject.factory(type, :repository => self, :id => id, :data => content)
@objects.insert(id, object)
object
end
- def get_tree(id) get_type(id, 'tree') end
- def get_blob(id) get_type(id, 'blob') end
- def get_commit(id) get_type(id, 'commit') end
+ def get_tree(id) get_type(id, :tree) end
+ def get_blob(id) get_type(id, :blob) end
+ def get_commit(id) get_type(id, :commit) end
# Write a raw object to the repository.
#
# Returns the object.
def put(object)
@@ -256,26 +245,26 @@
def method_missing(name, *args, &block)
cmd = name.to_s
if cmd[0..3] == 'git_'
ENV['GIT_DIR'] = path
- args = args.flatten.compact.map {|s| "'" + s.to_s.gsub("'", "'\\\\''") + "'" }.join(' ')
cmd = cmd[4..-1].tr('_', '-')
- cmd = "git #{cmd} #{args} 2>&1"
+ args = args.flatten.compact.map {|s| "'" + s.to_s.gsub("'", "'\\\\''") + "'" }.join(' ')
+ cmdline = "git #{cmd} #{args} 2>&1"
- @logger.debug "gitrb: #{cmd}"
+ @logger.debug "gitrb: #{cmdline}"
# Read in binary mode (ascii-8bit) and convert afterwards
out = if block_given?
- IO.popen(cmd, 'rb', &block)
+ IO.popen(cmdline, 'rb', &block)
else
- set_encoding IO.popen(cmd, 'rb') {|io| io.read }
+ set_encoding IO.popen(cmdline, 'rb') {|io| io.read }
end
if $?.exitstatus > 0
return '' if $?.exitstatus == 1 && out == ''
- raise RuntimeError, "#{cmd}: #{out}"
+ raise CommandError.new("git #{cmd}", args, out)
end
out
else
super
@@ -303,20 +292,46 @@
load
self
end
end
- protected
+ private
+ def check_git_version
+ version = git_version
+ raise "Invalid git version: #{version}" if version !~ /^git version ([\d\.]+)$/
+ a = $1.split('.').map {|s| s.to_i }
+ b = MIN_GIT_VERSION.split('.').map {|s| s.to_i }
+ while !a.empty? && !b.empty? && a.first == b.first
+ a.shift
+ b.shift
+ end
+ raise "Minimum required git version is #{MIN_GIT_VERSION}" if a.first.to_i < b.first.to_i
+ end
+
+ def open_repository(create)
+ if create && !File.exists?("#{@path}/objects")
+ FileUtils.mkpath(@path) if !File.exists?(@path)
+ raise ArgumentError, "Not a valid Git repository: '#{@path}'" if !File.directory?(@path)
+ if @bare
+ Dir.chdir(@path) { git_init '--bare' }
+ else
+ Dir.chdir(@path[0..-6]) { git_init }
+ end
+ else
+ raise ArgumentError, "Not a valid Git repository: '#{@path}'" if !File.directory?("#{@path}/objects")
+ end
+ end
+
# Start a transaction.
#
# Tries to get lock on lock file, load the this repository if
# has changed in the repository.
def start_transaction
- file = File.open("#{head_path}.lock", "w")
+ file = File.open("#{head_path}.lock", 'w')
file.flock(File::LOCK_EX)
- Thread.current['gitrb_repository_lock'] = file
+ Thread.current[LOCK] = file
refresh
end
# Rerepository the state of the repository.
#
@@ -329,12 +344,12 @@
# Finish the transaction.
#
# Release the lock file.
def finish_transaction
- Thread.current['gitrb_repository_lock'].close rescue nil
- Thread.current['gitrb_repository_lock'] = nil
+ Thread.current[LOCK].close rescue nil
+ Thread.current[LOCK] = nil
File.unlink("#{head_path}.lock") rescue nil
end
def get_type(id, expected)
object = get(id)
@@ -390,11 +405,11 @@
# Returns the object id of the last commit.
def read_head_id
if File.exists?(head_path)
File.read(head_path).strip
elsif File.exists?("#{path}/packed-refs")
- File.open("#{path}/packed-refs", "rb") do |io|
+ File.open("#{path}/packed-refs", 'rb') do |io|
while line = io.gets
line.strip!
next if line[0..0] == '#'
line = line.split(' ')
return line[0] if line[1] == "refs/heads/#{branch}"
@@ -402,10 +417,10 @@
end
end
end
def write_head_id(id)
- File.open(head_path, "wb") {|file| file.write(id) }
+ File.open(head_path, 'wb') {|file| file.write(id) }
end
def legacy_loose_object?(buf)
buf[0].ord == 0x78 && ((buf[0].ord << 8) | buf[1].ord) % 31 == 0
end