Permalink
Browse files

added gitr to the gem package as a binary, beginning of http-fetch im…

…plementation

can fully clone a loose object git repo over http
  • Loading branch information...
1 parent b31112e commit 80c599f671e19e4ebdd7c80d8fb4a72ecea19924 @schacon committed Mar 31, 2008
View
@@ -10,8 +10,13 @@ spec = Gem::Specification.new do |s|
s.email = "schacon@gmail.com"
s.summary = "A pure ruby implementation of Git"
s.files = FileList['lib/**/*', 'tests/**/*', 'doc/**/*'].to_a
+
+ s.bindir = 'bin'
+ s.executables << "gitr"
+ s.homepage = "http://github/schacon/git-ruby"
+
s.require_path = "lib"
- s.autorequire = "git"
+ s.autorequire = "git-ruby"
s.test_files = Dir.glob('tests/*.rb')
s.has_rdoc = true
s.extra_rdoc_files = ["README"]
View
5 TODO
@@ -1,8 +1,3 @@
-== first pass ==
-
-repo.add(@name)
-repo.commit(message)
-
== second pass ==
$repo.log.first.gtree.children.map
View
@@ -23,6 +23,7 @@
require 'git-ruby/raw/repository'
require 'fileutils'
+require 'logger'
# Git-Ruby Library
@@ -68,5 +69,19 @@ def self.open(working_dir, options = {})
def self.init(working_dir = '.', options = {})
Base.init(working_dir, options)
end
+
+ # clones a remote repository
+ #
+ # options
+ # :bare => true (does a bare clone)
+ # :repository => '/path/to/alt_git_dir'
+ # :index => '/path/to/alt_index_file'
+ #
+ # example
+ # Git.clone('git://repo.or.cz/rubygit.git', 'clone.git', :bare => true)
+ #
+ def self.clone(repository, name, options = {})
+ Base.clone(repository, name, options)
+ end
end
View
@@ -35,7 +35,7 @@ def self.open(working_dir, opts={})
def self.init(working_dir, opts = {})
default = {:working_directory => File.expand_path(working_dir),
:repository => File.join(working_dir, '.git')}
- git_options = default.merge(opts)
+ git_options = default.merge(opts)
if git_options[:working_directory]
# if !working_dir, make it
@@ -49,15 +49,34 @@ def self.init(working_dir, opts = {})
GitRuby::Repository.init(git_options[:repository])
self.new(git_options)
- end
-
+ end
+
+ # clones a git repository locally
+ #
+ # repository - http://repo.or.cz/w/sinatra.git
+ # name - sinatra
+ #
+ # options:
+ # :repository
+ #
+ # :bare
+ # or
+ # :working_directory
+ # :index_file
+ #
+ def self.clone(repository, name, opts = {})
+ # run git-clone
+ self.new(GitRuby::Lib.new(nil, opts[:logger]).clone(repository, name, opts))
+ end
+
+
def initialize(options = {})
if working_dir = options[:working_directory]
options[:repository] = File.join(working_dir, '.git') if !options[:repository]
options[:index] = File.join(working_dir, '.git', 'index') if !options[:index]
end
- if options[:log]
- @logger = options[:log]
+ if options[:logger]
+ @logger = options[:logger]
@logger.info("Starting Git")
end
@@ -228,8 +247,17 @@ def ls_files
self.index.ls_files
end
- def add(file)
- self.lib.add(file)
+ def add(file = '.')
+ if file == '.'
+ # add all files
+ Dir.glob("**/*").each do |file|
+ if File.file?(file)
+ self.lib.add(file)
+ end
+ end
+ else
+ self.lib.add(file)
+ end
end
def commit(message)
View
@@ -1,9 +1,9 @@
require 'tempfile'
+require 'net/https'
module GitRuby
- class GitRubyExecuteError < StandardError
- end
+ class GitRubyInvalidTransport < StandardError; end
class Lib
@@ -30,6 +30,147 @@ def initialize(base = nil, logger = nil)
end
end
+
+ # tries to clone the given repo
+ #
+ # returns {:repository} (if bare)
+ # {:working_directory} otherwise
+ #
+ # accepts options:
+ # :remote - name of remote (rather than 'origin')
+ # :bare - no working directory
+ #
+ # TODO - make this work with SSH password or auth_key
+ #
+ def clone(repository, name, opts = {})
+ @path = opts[:path] || '.'
+ opts[:path] ? clone_dir = File.join(@path, name) : clone_dir = name
+
+ working_dir = clone_dir
+
+ # initialize repository
+ if(!opts[:bare])
+ clone_dir += '/.git'
+ end
+
+ GitRuby::Repository.init(clone_dir, opts[:bare])
+ @git_dir = File.expand_path(clone_dir)
+
+ remote_name = opts[:remote] || 'origin'
+
+ # look at #{repository} for http://, user@, git://
+ if repository =~ /^http:\/\//
+ # http fetch
+ clone_http(repository, false, remote_name, clone_dir)
+ elsif repository =~ /^https:\/\//
+ # https fetch
+ clone_http(repository, true, remote_name, clone_dir)
+ elsif repository =~ /^git:\/\//
+ # git fetch
+ raise GitRubyInvalidTransport('transport git:// not yet supported')
+ else
+ raise GitRubyInvalidTransport('unknown transport')
+ end
+
+ if opts[:bare]
+ return {:repository => clone_dir}
+ else
+ # !! TODO : checkout to working_dir !!
+ return {:working_directory => working_dir}
+ end
+ end
+
+ # implements cloning a repository over http/s
+ # meant to be called
+ def clone_http(repo_url, use_ssl, remote_name, clone_dir)
+ # refs : 909e4d4f706c11cafbe35fd9729dc6cce24d6d6f refs/heads/master
+ # packs: P pack-8607f42392be437e8f46408898de44948ccd357f.pack
+
+ Dir.chdir(clone_dir) do
+ # fetch (url)/info/refs
+ log('fetching server refs')
+ refs = Net::HTTP.get(URI.parse("#{repo_url}/info/refs"))
+ fetch_refs = map_refs(refs)
+
+ # fetch (url)/HEAD, write as FETCH_HEAD
+ log('fetching remote HEAD')
+ remote_head = Net::HTTP.get(URI.parse("#{repo_url}/HEAD"))
+ if !(remote_head =~ /^ref: refs\//)
+ fetch_refs[remote_head] = false
+ end
+
+ fetch_refs.each do |sha, ref|
+ log("fetching REF : #{ref} #{sha}")
+ if http_fetch(repo_url, sha, 'commit')
+ puts 'UPDATE REF'
+ update_ref("refs/remotes/#{remote_name}/#{ref}", sha) if ref
+ end
+ end
+
+ end
+ end
+
+ def map_refs(refs)
+ # process the refs file
+ # get a list of all the refs/heads/
+ fetch_refs = {}
+ refs.split("\n").each do |ref|
+ if ref =~ /refs\/heads/
+ sha, head = ref.split("\t")
+ head = head.sub('refs/heads/', '').strip
+ fetch_refs[sha] = head
+ end
+ end
+ fetch_refs
+ end
+
+ def http_fetch(url, sha, type)
+ # fetch from server objects/sh/a1value
+ dir = sha[0...2]
+ obj = sha[2..40]
+
+ path = File.join('objects', dir)
+
+ if !get_raw_repo.object_exists?(sha)
+ res = Net::HTTP.get_response(URI.parse("#{url}/objects/#{dir}/#{obj}"))
+ if res.kind_of?(Net::HTTPSuccess)
+ Dir.mkdir(path) if !File.directory?(path)
+ write_file(File.join('objects', dir, obj), res.body)
+ log("#{type} : #{sha} fetched")
+ else
+ # file may be packed - get the packfiles if we haven't already and lets try those
+ # fetch (url)/objects/info/packs
+ # fetch packs we don't have, look for it there
+ puts "FAIL #{sha}" + res.to_s
+ return false
+ end
+ end
+
+ response = true
+
+ case type
+ when 'commit':
+ # if it's a commit, walk the tree, then get it's parents
+ commit = commit_data(sha)
+ log('walking ' + commit['tree'])
+ http_fetch(url, commit['tree'], 'tree')
+ commit['parent'].each do |parent|
+ log('walking ' + parent)
+ response &&= http_fetch(url, parent, 'commit')
+ end
+ when 'tree':
+ data = ls_tree(sha)
+ data['blob'].each do |key, blob|
+ response &&= http_fetch(url, blob[:sha], 'blob')
+ end
+ data['tree'].each do |key, tree|
+ response &&= http_fetch(url, tree[:sha], 'tree')
+ end
+ end
+
+ response
+ end
+
## READ COMMANDS ##
def process_commit_data(data, sha = nil)
@@ -101,7 +242,8 @@ def revparse(string)
head = File.join(@git_dir, 'refs', 'tags', string)
return File.read(head).chomp if File.file?(head)
- ## !! more !!
+ ## !! check packed-refs file, too !!
+ ## !! more - partials and such !!
return string
end
@@ -143,7 +285,7 @@ def write_commit_info(tree, parents, message)
contents = []
contents << ['tree', tree].join(' ')
parents.each do |p|
- contents << ['parent', p].join(' ')
+ contents << ['parent', p].join(' ') if p
end
name = config_get('user.name')
@@ -171,8 +313,9 @@ def formatted_offset
def update_ref(ref, sha)
ref_file = File.join(@git_dir, ref)
- return false if !File.exists?(ref_file)
-
+ if(!File.exists?(ref))
+ FileUtils.mkdir_p(File.basedir(ref_file)) rescue nil
+ end
File.open(ref_file, 'w') do |f|
f.write sha
end
@@ -266,6 +409,16 @@ def tags
Dir.chdir(tag_dir) { tags = Dir.glob('*') }
return tags
end
+
+ def log(message)
+ @logger.info(message) if @logger
+ end
+
+ def write_file(name, contents)
+ File.open(name, 'w') do |f|
+ f.write contents
+ end
+ end
end
end
@@ -26,9 +26,8 @@ def initialize(directory)
def [](sha1)
sha1 = sha1.unpack("H*")[0]
-
- path = @directory+'/'+sha1[0...2]+'/'+sha1[2..40]
begin
+ path = @directory+'/'+sha1[0...2]+'/'+sha1[2..40]
get_raw_object(File.read(path))
rescue Errno::ENOENT
nil
@@ -91,7 +91,8 @@ def each_sha1
def find_object(sha1)
slot = sha1[0]
- first, last = @offsets[slot,2]
+ return nil if !slot
+ first, last = @offsets[slot,2]
while first < last
mid = (first + last) / 2
midsha1 = @idx[SHA1Start + mid * EntrySize,SHA1Size]
@@ -76,6 +76,27 @@ def put_raw_object(content, type)
@loose.put_raw_object(content, type)
end
+ def object_exists?(sha1)
+ sha_hex = [sha1].pack("H*")
+ return true if in_packs?(sha_hex)
+ return true if in_loose?(sha_hex)
+ return true if in_packs?(sha_hex) #maybe the object got packed in the meantime
+ false
+ end
+
+ def in_packs?(sha_hex)
+ # try packs
+ @packs.each do |pack|
+ return true if pack[sha_hex]
+ end
+ false
+ end
+
+ def in_loose?(sha_hex)
+ return true if @loose[sha_hex]
+ false
+ end
+
def get_raw_object_by_sha1(sha1)
sha1 = [sha1].pack("H*")
@@ -112,11 +133,13 @@ def initpacks
pack.close
end
@packs = []
- Dir.open(git_path("objects/pack/")) do |dir|
- dir.each do |entry|
- if entry =~ /\.pack$/i
- @packs << GitRuby::Raw::Internal::PackStorage.new(git_path("objects/pack/" \
- + entry))
+ if File.exists?(git_path("objects/pack"))
+ Dir.open(git_path("objects/pack/")) do |dir|
+ dir.each do |entry|
+ if entry =~ /\.pack$/i
+ @packs << GitRuby::Raw::Internal::PackStorage.new(git_path("objects/pack/" \
+ + entry))
+ end
end
end
end
Oops, something went wrong.

0 comments on commit 80c599f

Please sign in to comment.