Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

my new commit

  • Loading branch information...
commit 469b8bc2b21fbe4ba7b14486675e4446e8002268 1 parent 842649b
Scott Chacon schacon authored

Showing 1 changed file with 203 additions and 30 deletions. Show diff stats Hide diff stats

  1. +203 30 receive-pack.rb
233 receive-pack.rb
@@ -2,13 +2,16 @@
2 2 require 'socket'
3 3 require 'pp'
4 4 require 'zlib'
  5 +require 'fileutils'
  6 +require 'digest'
5 7
6 8 class GitServer
7 9
8 10 NULL_SHA = '0000000000000000000000000000000000000000'
9 11 #CAPABILITIES = " report-status delete-refs "
  12 + #CAPABILITIES = " multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress "
10 13 CAPABILITIES = " "
11   -
  14 +
12 15 OBJ_NONE = 0
13 16 OBJ_COMMIT = 1
14 17 OBJ_TREE = 2
@@ -19,15 +22,19 @@ class GitServer
19 22
20 23 OBJ_TYPES = [nil, :commit, :tree, :blob, :tag, nil, :ofs_delta, :ref_delta].freeze
21 24
22   - def self.start_server
23   - server = self.new
  25 + def initialize(path)
  26 + @path = path
  27 + end
  28 +
  29 + def self.start_server(path)
  30 + server = self.new(path)
24 31 server.listen
25 32 end
26 33
27 34 def listen
28 35 server = TCPServer.new('127.0.0.1', 9418)
29 36 while (session = server.accept)
30   - t = GitServerThread.new(session)
  37 + t = GitServerThread.new(session, @path)
31 38 t.do_action
32 39 return
33 40 end
@@ -35,7 +42,8 @@ def listen
35 42
36 43 class GitServerThread
37 44
38   - def initialize(session)
  45 + def initialize(session, path)
  46 + @path = path
39 47 @session = session
40 48 @capabilities_sent = false
41 49 end
@@ -54,37 +62,88 @@ def do_action
54 62 end
55 63
56 64 def receive_pack(path)
57   - puts "REC PACK: #{path}"
  65 + @delta_list = {}
  66 +
  67 + @git_dir = File.join(@path, path)
  68 + git_init(@git_dir) if !File.exists?(@git_dir)
  69 +
58 70 send_refs
59 71 packet_flush
60 72 read_refs
61 73 read_pack
  74 + write_refs
  75 + end
  76 +
  77 + def write_refs
  78 + @refs.each do |sha_old, sha_new, path|
  79 + ref = File.join(@git_dir, path)
  80 + FileUtils.mkdir_p(File.dirname(ref))
  81 + File.open(ref, 'w+') { |f| f.write(sha_new) }
  82 + end
  83 + end
  84 +
  85 + def git_init(dir, bare = false)
  86 + FileUtils.mkdir_p(dir) if !File.exists?(dir)
  87 +
  88 + FileUtils.cd(dir) do
  89 + if(File.exists?('objects'))
  90 + return false # already initialized
  91 + else
  92 + # initialize directory
  93 + create_initial_config(bare)
  94 + FileUtils.mkdir_p('refs/heads')
  95 + FileUtils.mkdir_p('refs/tags')
  96 + FileUtils.mkdir_p('objects/info')
  97 + FileUtils.mkdir_p('objects/pack')
  98 + FileUtils.mkdir_p('branches')
  99 + add_file('description', 'Unnamed repository; edit this file to name it for gitweb.')
  100 + add_file('HEAD', "ref: refs/heads/master\n")
  101 + FileUtils.mkdir_p('hooks')
  102 + FileUtils.cd('hooks') do
  103 + add_file('applypatch-msg', '# add shell script and make executable to enable')
  104 + add_file('post-commit', '# add shell script and make executable to enable')
  105 + add_file('post-receive', '# add shell script and make executable to enable')
  106 + add_file('post-update', '# add shell script and make executable to enable')
  107 + add_file('pre-applypatch', '# add shell script and make executable to enable')
  108 + add_file('pre-commit', '# add shell script and make executable to enable')
  109 + add_file('pre-rebase', '# add shell script and make executable to enable')
  110 + add_file('update', '# add shell script and make executable to enable')
  111 + end
  112 + FileUtils.mkdir_p('info')
  113 + add_file('info/exclude', "# *.[oa]\n# *~")
  114 + end
  115 + end
  116 + end
  117 +
  118 + def create_initial_config(bare = false)
  119 + bare ? bare_status = 'true' : bare_status = 'false'
  120 + config = "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = #{bare_status}\n\tlogallrefupdates = true"
  121 + add_file('config', config)
  122 + end
  123 +
  124 + def add_file(name, contents)
  125 + File.open(name, 'w') do |f|
  126 + f.write contents
  127 + end
62 128 end
63 129
64 130 def read_refs
65   - headers = []
  131 + @refs = []
66 132 while(data = packet_read_line) do
67 133 sha_old, sha_new, path = data.split(' ')
68   - headers << [sha_old, sha_new, path]
  134 + @refs << [sha_old, sha_new, path]
69 135 end
70   - pp headers
71 136 end
72 137
73 138 def read_pack
74 139 (sig, ver, entries) = read_pack_header
75   -
76   - puts "SIG: #{sig}"
77   - puts "VER: #{ver}"
78   - puts "ENT: #{entries}"
79   - puts
80   -
81 140 unpack_all(entries)
82 141 end
83 142
84 143 def unpack_all(entries)
85 144 1.upto(entries) do |number|
86 145 unpack_object(number)
87   - end
  146 + end if entries
88 147 end
89 148
90 149 def unpack_object(number)
@@ -101,11 +160,11 @@ def unpack_object(number)
101 160 case type
102 161 when OBJ_OFS_DELTA, OBJ_REF_DELTA
103 162 puts "WRITE " + OBJ_TYPES[type].to_s
104   - unpack_deltified(type, size)
  163 + sha = unpack_deltified(type, size)
105 164 return
106 165 when OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG
107   - puts "WRITE " + OBJ_TYPES[type].to_s
108   - unpack_compressed(type, size)
  166 + #puts "WRITE " + OBJ_TYPES[type].to_s
  167 + sha = unpack_compressed(type, size)
109 168 return
110 169 else
111 170 puts "invalid type #{type}"
@@ -114,12 +173,120 @@ def unpack_object(number)
114 173
115 174 def unpack_compressed(type, size)
116 175 object_data = get_data(size)
  176 + sha = put_raw_object(object_data, OBJ_TYPES[type].to_s)
  177 + check_delta(sha)
  178 + end
  179 +
  180 + def check_delta(sha)
  181 + unpack_delta_cached(sha) if @delta_list[sha]
  182 + sha
  183 + end
  184 +
  185 + def unpack_delta_cached(sha)
  186 + base, type = get_raw_object(sha)
  187 + @delta_list[sha].each do |patch|
  188 + obj_data = patch_delta(base, patch)
  189 + sha = put_raw_object(obj_data, type)
  190 + check_delta(sha)
  191 + end
  192 + @delta_list[sha] = nil
  193 + end
  194 +
  195 + def has_object?(sha1)
  196 + File.exists?(File.join(@git_dir, 'objects', sha1[0...2], sha1[2..39]))
  197 + end
  198 +
  199 + def get_raw_object(sha1)
  200 + path = File.join(@git_dir, 'objects', sha1[0...2], sha1[2..39])
  201 + buf = File.read(path)
  202 +
  203 + if buf.length < 2
  204 + puts "object file too small"
  205 + end
  206 +
  207 + if legacy_loose_object?(buf)
  208 + content = Zlib::Inflate.inflate(buf)
  209 + header, content = content.split(/\0/, 2)
  210 + if !header || !content
  211 + puts "invalid object header"
  212 + end
  213 + type, size = header.split(/ /, 2)
  214 + if !%w(blob tree commit tag).include?(type) || size !~ /^\d+$/
  215 + puts "invalid object header"
  216 + end
  217 + type = type.to_sym
  218 + size = size.to_i
  219 + else
  220 + type, size, used = unpack_object_header_gently(buf)
  221 + content = Zlib::Inflate.inflate(buf[used..-1])
  222 + end
  223 + puts "size mismatch" if content.length != size
  224 + return [content, type]
  225 + end
  226 +
  227 + def legacy_loose_object?(buf)
  228 + word = (buf[0] << 8) + buf[1]
  229 + buf[0] == 0x78 && word % 31 == 0
117 230 end
  231 +
  232 + def unpack_object_header_gently(buf)
  233 + used = 0
  234 + c = buf[used]
  235 + used += 1
118 236
  237 + type = (c >> 4) & 7;
  238 + size = c & 15;
  239 + shift = 4;
  240 + while c & 0x80 != 0
  241 + if buf.length <= used
  242 + raise LooseObjectError, "object file too short"
  243 + end
  244 + c = buf[used]
  245 + used += 1
  246 +
  247 + size += (c & 0x7f) << shift
  248 + shift += 7
  249 + end
  250 + type = OBJ_TYPES[type]
  251 + if ![:blob, :tree, :commit, :tag].include?(type)
  252 + raise LooseObjectError, "invalid loose object type"
  253 + end
  254 + return [type, size, used]
  255 + end
  256 +
  257 + def put_raw_object(content, type)
  258 + size = content.length.to_s
  259 +
  260 + header = "#{type} #{size}\0"
  261 + store = header + content
  262 +
  263 + sha1 = Digest::SHA1.hexdigest(store)
  264 + path = File.join(@git_dir, 'objects', sha1[0...2], sha1[2..40])
  265 +
  266 + if !File.exists?(path)
  267 + content = Zlib::Deflate.deflate(store)
  268 +
  269 + FileUtils.mkdir_p(File.join(@git_dir, 'objects', sha1[0...2]))
  270 + File.open(path, 'w') do |f|
  271 + f.write content
  272 + end
  273 + end
  274 + return sha1
  275 + end
  276 +
119 277 def unpack_deltified(type, size)
120 278 if type == OBJ_REF_DELTA
121 279 base_sha = @session.recv(20)
122   - object_data = get_data(size)
  280 + sha1 = base_sha.unpack("H*")[0]
  281 + delta = get_data(size)
  282 + if has_object?(sha1)
  283 + base, type = get_raw_object(sha1)
  284 + obj_data = patch_delta(base, delta)
  285 + return put_raw_object(obj_data, type)
  286 + else
  287 + @delta_list[sha1] ||= []
  288 + @delta_list[sha1] << delta
  289 + end
123 290 else
124 291 i = 0
125 292 c = data[i]
@@ -131,12 +298,9 @@ def unpack_deltified(type, size)
131 298 base_offset |= c & 0x7f
132 299 end
133 300 offset += i + 1
  301 + return false ## NOT SUPPORTED YET ##
134 302 end
135   -
136   - return false
137   -
138   - base, type = unpack_object(packfile, base_offset)
139   - [patch_delta(base, delta), type]
  303 + return nil
140 304 end
141 305
142 306 def get_data(size)
@@ -223,12 +387,17 @@ def packet_flush
223 387 end
224 388
225 389 def refs
226   - []
  390 + @refs = []
  391 + Dir.chdir(@git_dir) do
  392 + Dir.glob("refs/**/*") do |file|
  393 + @refs << [File.read(file), file] if File.file?(file)
  394 + end
  395 + end
  396 + @refs
227 397 end
228 398
229 399 def send_refs
230 400 refs.each do |ref|
231   - puts ref
232 401 send_ref(ref[1], ref[0])
233 402 end
234 403 send_ref("capabilities^{}", NULL_SHA) if !@capabiliies_sent
@@ -254,8 +423,11 @@ def write_server(data)
254 423
255 424 def upload_pack(path)
256 425 puts "UPL PACK"
  426 + send_refs
  427 + read_refs
  428 + upload_pack_file
257 429 end
258   -
  430 +
259 431 def read_header()
260 432 len = @session.recv( 4 ).hex
261 433 return false if (len == 0)
@@ -268,7 +440,7 @@ def read_header()
268 440 def read_until_null(debug = false)
269 441 data = ''
270 442 while c = @session.recv(1)
271   - puts "read: #{c}:#{c[0]}" if debug
  443 + #puts "read: #{c}:#{c[0]}" if debug
272 444 if c[0] == 0
273 445 return data
274 446 else
@@ -282,5 +454,6 @@ def read_until_null(debug = false)
282 454 end
283 455 end
284 456
285   -GitServer.start_server
  457 +#FileUtils.rm_r('/tmp/gittest') rescue nil
  458 +GitServer.start_server('/tmp/gittest')
286 459

0 comments on commit 469b8bc

Please sign in to comment.
Something went wrong with that request. Please try again.