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