git-object-browser 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +13 -0
  4. data/Guardfile +11 -0
  5. data/LICENSE +22 -0
  6. data/README.md +18 -0
  7. data/Rakefile +2 -0
  8. data/bin/git-object-browser +5 -0
  9. data/git-object-browser.gemspec +18 -0
  10. data/htdocs/css/angular-ui.min.css +1 -0
  11. data/htdocs/css/bootstrap-responsive.css +1058 -0
  12. data/htdocs/css/bootstrap-responsive.min.css +9 -0
  13. data/htdocs/css/bootstrap.css +5389 -0
  14. data/htdocs/css/bootstrap.min.css +699 -0
  15. data/htdocs/css/font-awesome-ie7.css +645 -0
  16. data/htdocs/css/font-awesome.css +303 -0
  17. data/htdocs/css/main.css +22 -0
  18. data/htdocs/font/fontawesome-webfont.eot +0 -0
  19. data/htdocs/font/fontawesome-webfont.svg +255 -0
  20. data/htdocs/font/fontawesome-webfont.ttf +0 -0
  21. data/htdocs/font/fontawesome-webfont.woff +0 -0
  22. data/htdocs/img/glyphicons-halflings-white.png +0 -0
  23. data/htdocs/img/glyphicons-halflings.png +0 -0
  24. data/htdocs/index.html +114 -0
  25. data/htdocs/js/main.js +286 -0
  26. data/htdocs/js/vendor/angular-bootstrap-prettify.min.js +41 -0
  27. data/htdocs/js/vendor/angular-bootstrap.min.js +9 -0
  28. data/htdocs/js/vendor/angular-cookies.min.js +7 -0
  29. data/htdocs/js/vendor/angular-loader.min.js +7 -0
  30. data/htdocs/js/vendor/angular-resource.min.js +10 -0
  31. data/htdocs/js/vendor/angular-sanitize.min.js +13 -0
  32. data/htdocs/js/vendor/angular-ui-ieshiv.min.js +7 -0
  33. data/htdocs/js/vendor/angular-ui.min.js +7 -0
  34. data/htdocs/js/vendor/angular.js +14401 -0
  35. data/htdocs/js/vendor/angular.min.js +158 -0
  36. data/htdocs/js/vendor/bootstrap.js +2027 -0
  37. data/htdocs/js/vendor/bootstrap.min.js +6 -0
  38. data/htdocs/js/vendor/html5shiv.js +5 -0
  39. data/htdocs/js/vendor/jquery-1.8.2.min.js +2 -0
  40. data/htdocs/templates/directory.html +25 -0
  41. data/htdocs/templates/file.html +6 -0
  42. data/htdocs/templates/git.html +1 -0
  43. data/htdocs/templates/index.html +44 -0
  44. data/htdocs/templates/index_entry.html +54 -0
  45. data/htdocs/templates/info_refs.html +27 -0
  46. data/htdocs/templates/loading.html +1 -0
  47. data/htdocs/templates/notfound.html +7 -0
  48. data/htdocs/templates/object.html +54 -0
  49. data/htdocs/templates/objects.html +16 -0
  50. data/htdocs/templates/pack_file.html +15 -0
  51. data/htdocs/templates/pack_index.html +38 -0
  52. data/htdocs/templates/packed_object.html +111 -0
  53. data/htdocs/templates/packed_refs.html +27 -0
  54. data/htdocs/templates/ref.html +12 -0
  55. data/lib/git-object-browser.rb +37 -0
  56. data/lib/git-object-browser/dumper.rb +62 -0
  57. data/lib/git-object-browser/index_dumper.rb +77 -0
  58. data/lib/git-object-browser/main.rb +63 -0
  59. data/lib/git-object-browser/models/bindata.rb +68 -0
  60. data/lib/git-object-browser/models/directory.rb +61 -0
  61. data/lib/git-object-browser/models/git_object.rb +127 -0
  62. data/lib/git-object-browser/models/index.rb +77 -0
  63. data/lib/git-object-browser/models/index_entry.rb +95 -0
  64. data/lib/git-object-browser/models/index_reuc_extension.rb +31 -0
  65. data/lib/git-object-browser/models/index_tree_extension.rb +52 -0
  66. data/lib/git-object-browser/models/info_refs.rb +32 -0
  67. data/lib/git-object-browser/models/pack_file.rb +40 -0
  68. data/lib/git-object-browser/models/pack_index.rb +146 -0
  69. data/lib/git-object-browser/models/packed_object.rb +239 -0
  70. data/lib/git-object-browser/models/packed_refs.rb +39 -0
  71. data/lib/git-object-browser/models/plain_file.rb +24 -0
  72. data/lib/git-object-browser/models/ref.rb +32 -0
  73. data/lib/git-object-browser/object_dumper.rb +31 -0
  74. data/lib/git-object-browser/server/git_servlet.rb +209 -0
  75. data/lib/git-object-browser/server/main.rb +31 -0
  76. data/lib/git-object-browser/version.rb +3 -0
  77. data/spec/fixtures/generate_worktree.sh +103 -0
  78. data/spec/fixtures/git/indexes/001 +0 -0
  79. data/spec/fixtures/git/indexes/002-empty-tree-extension +0 -0
  80. data/spec/fixtures/git/plain_file +1 -0
  81. data/spec/fixtures/json/blob.json +9 -0
  82. data/spec/fixtures/json/merge-a.json +48 -0
  83. data/spec/fixtures/json/test3-tag.json +36 -0
  84. data/spec/fixtures/json/tree.json +20 -0
  85. data/spec/fixtures/worktree/_git/COMMIT_EDITMSG +1 -0
  86. data/spec/fixtures/worktree/_git/HEAD +1 -0
  87. data/spec/fixtures/worktree/_git/ORIG_HEAD +1 -0
  88. data/spec/fixtures/worktree/_git/config +11 -0
  89. data/spec/fixtures/worktree/_git/description +1 -0
  90. data/spec/fixtures/worktree/_git/hooks/applypatch-msg.sample +15 -0
  91. data/spec/fixtures/worktree/_git/hooks/commit-msg.sample +24 -0
  92. data/spec/fixtures/worktree/_git/hooks/post-update.sample +8 -0
  93. data/spec/fixtures/worktree/_git/hooks/pre-applypatch.sample +14 -0
  94. data/spec/fixtures/worktree/_git/hooks/pre-commit.sample +50 -0
  95. data/spec/fixtures/worktree/_git/hooks/pre-rebase.sample +169 -0
  96. data/spec/fixtures/worktree/_git/hooks/prepare-commit-msg.sample +36 -0
  97. data/spec/fixtures/worktree/_git/hooks/update.sample +128 -0
  98. data/spec/fixtures/worktree/_git/ignore +3 -0
  99. data/spec/fixtures/worktree/_git/index +0 -0
  100. data/spec/fixtures/worktree/_git/info/exclude +6 -0
  101. data/spec/fixtures/worktree/_git/info/refs +1 -0
  102. data/spec/fixtures/worktree/_git/logs/HEAD +10 -0
  103. data/spec/fixtures/worktree/_git/logs/refs/heads/branch-a +3 -0
  104. data/spec/fixtures/worktree/_git/logs/refs/heads/branch-b +2 -0
  105. data/spec/fixtures/worktree/_git/logs/refs/heads/master +4 -0
  106. data/spec/fixtures/worktree/_git/objects/00/cb8bfeb5b8ce906d39698e4e33b38341f5448f +1 -0
  107. data/spec/fixtures/worktree/_git/objects/07/31f9d4b6fa0475872be6a8ca263096f1d201cf +2 -0
  108. data/spec/fixtures/worktree/_git/objects/1d/3dc60b5a117054e43741d51e599ff31bb15f9f +0 -0
  109. data/spec/fixtures/worktree/_git/objects/26/4e42b1fef5bcb55acec162fdd5a068d79ae551 +0 -0
  110. data/spec/fixtures/worktree/_git/objects/28/3c06ddf1b31c14bb221d41173299e133b7753d +0 -0
  111. data/spec/fixtures/worktree/_git/objects/37/d1632d3f1159dad9cfb58e6c34312ab4355c49 +0 -0
  112. data/spec/fixtures/worktree/_git/objects/3a/2bf444f105c19b13ba5e75e884e10715e95a91 +0 -0
  113. data/spec/fixtures/worktree/_git/objects/40/a061aaf0cd0555449671a11993e4fed11f91a4 +2 -0
  114. data/spec/fixtures/worktree/_git/objects/53/2a1874c26cd19bd4d66e03218ab73e63de4357 +3 -0
  115. data/spec/fixtures/worktree/_git/objects/5b/719b165fde1964fb5a08adaf3b6e4f57ca1ff5 +0 -0
  116. data/spec/fixtures/worktree/_git/objects/61/cae34206bb889bae43ffdd22c17217485178bf +0 -0
  117. data/spec/fixtures/worktree/_git/objects/6c/444ac15f1e3c2a6869bd36ca7e58c39512106f +2 -0
  118. data/spec/fixtures/worktree/_git/objects/93/b714995d24c52180195876058a49c7d7fea0ad +0 -0
  119. data/spec/fixtures/worktree/_git/objects/96/25401ac3e19ef10868c140a76b719ac3f08fcf +0 -0
  120. data/spec/fixtures/worktree/_git/objects/b9/29ed2ab14f7489d5238a06d10d2f2c229a4ab4 +0 -0
  121. data/spec/fixtures/worktree/_git/objects/be/e0d26d33c284ee065e38bd7e81ae4bdc870f89 +4 -0
  122. data/spec/fixtures/worktree/_git/objects/c3/6491256978d26c08cd7aa97eee0f5631f96659 +0 -0
  123. data/spec/fixtures/worktree/_git/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 +0 -0
  124. data/spec/fixtures/worktree/_git/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b +0 -0
  125. data/spec/fixtures/worktree/_git/objects/e5/b6d4317cefa946d77fc91539f1f1e48b60836f +0 -0
  126. data/spec/fixtures/worktree/_git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
  127. data/spec/fixtures/worktree/_git/objects/info/packs +2 -0
  128. data/spec/fixtures/worktree/_git/objects/pack/pack-f1c1717e9264c12310c7bd3e7dcdd28924000ff6.idx +0 -0
  129. data/spec/fixtures/worktree/_git/objects/pack/pack-f1c1717e9264c12310c7bd3e7dcdd28924000ff6.pack +0 -0
  130. data/spec/fixtures/worktree/_git/packed-refs +2 -0
  131. data/spec/fixtures/worktree/_git/refs/heads/branch-a +1 -0
  132. data/spec/fixtures/worktree/_git/refs/heads/branch-b +1 -0
  133. data/spec/fixtures/worktree/_git/refs/heads/master +1 -0
  134. data/spec/fixtures/worktree/_git/refs/tags/simple-tag +1 -0
  135. data/spec/fixtures/worktree/_git/refs/tags/test3-tag +1 -0
  136. data/spec/fixtures/worktree/sample-a.txt +1 -0
  137. data/spec/fixtures/worktree/sample.txt +1 -0
  138. data/spec/fixtures/worktree/subdir/sample-sub.txt +0 -0
  139. data/spec/git-object-browser/main_spec.rb +31 -0
  140. data/spec/git-object-browser/models/bindata_spec.rb +144 -0
  141. data/spec/git-object-browser/models/git_object_spec.rb +49 -0
  142. data/spec/git-object-browser/models/index_spec.rb +42 -0
  143. data/spec/git-object-browser/models/index_tree_extension_spec.rb +58 -0
  144. data/spec/git-object-browser/models/plain_file_spec.rb +11 -0
  145. data/spec/spec_helper.rb +19 -0
  146. metadata +260 -0
@@ -0,0 +1,12 @@
1
+ <article>
2
+ <h1><i data-entry-icon="'ref'"></i> {{path}} <small>Reference</small></h1>
3
+
4
+ <table class="table">
5
+ <tr data-ng-show="object.sha1">
6
+ <th>sha1</th><td><a data-ref-href="object">{{object.sha1}}</a></td>
7
+ </tr>
8
+ <tr data-ng-show="object.ref">
9
+ <th>ref</th><td><a data-ref-href="object">{{object.ref}}</a></td>
10
+ </tr>
11
+ </table>
12
+ </article>
@@ -0,0 +1,37 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # http://book.git-scm.com/7_how_git_stores_objects.html
4
+ # http://code.google.com/p/git-core/source/browse/Documentation/technical/index-format.txt
5
+
6
+ require "git-object-browser/version"
7
+
8
+ require 'zlib'
9
+ require 'digest/sha1'
10
+ require 'json'
11
+ require 'time'
12
+ require 'stringio'
13
+ require 'optparse'
14
+
15
+ require "git-object-browser/main"
16
+ require "git-object-browser/models/ref"
17
+ require "git-object-browser/models/bindata"
18
+ require "git-object-browser/models/directory"
19
+ require "git-object-browser/models/git_object"
20
+ require "git-object-browser/models/index"
21
+ require "git-object-browser/models/index_entry"
22
+ require "git-object-browser/models/index_reuc_extension"
23
+ require "git-object-browser/models/index_tree_extension"
24
+ require "git-object-browser/models/info_refs"
25
+ require "git-object-browser/models/pack_file"
26
+ require "git-object-browser/models/pack_index"
27
+ require "git-object-browser/models/packed_object"
28
+ require "git-object-browser/models/packed_refs"
29
+ require "git-object-browser/models/plain_file"
30
+ require "git-object-browser/server/main"
31
+ require "git-object-browser/server/git_servlet"
32
+
33
+ require "git-object-browser/dumper"
34
+ require "git-object-browser/object_dumper"
35
+ require "git-object-browser/index_dumper"
36
+
37
+ GitObjectBrowser::Main.new().execute if __FILE__ == $0
@@ -0,0 +1,62 @@
1
+ module GitObjectBrowser
2
+
3
+ class Dumper
4
+
5
+ def initialize(target)
6
+ @target = target
7
+ @outdir = File.join(@target, "plain")
8
+ end
9
+
10
+ def dump
11
+ Dir.mkdir(@outdir) unless File.exist?(@outdir)
12
+ dump_index
13
+ dump_objects
14
+ end
15
+
16
+ def dump_index
17
+ index_file = File.join(@target, "index")
18
+ out_file = File.join(@outdir, "index")
19
+
20
+ return unless File.exist?(index_file)
21
+
22
+ STDERR << "Write: .git/plain/index\n"
23
+ File.open(index_file) do |input|
24
+ File.open(out_file, "w") do |output|
25
+ dumper = GitObjectBrowser::IndexDumper.new(input, output)
26
+ dumper.dump
27
+ end
28
+ end
29
+ end
30
+
31
+ def dump_objects
32
+ obj_files = []
33
+ Dir.chdir(@target) do
34
+ Dir.glob("objects/**/*") do |path|
35
+ obj_files << path if File.file?(path) && path =~ %r{/[a-z0-9]{38}$}
36
+ end
37
+ end
38
+ return if obj_files.empty?
39
+
40
+ obj_dir = File.join(@outdir, "objects")
41
+ Dir.mkdir(obj_dir) unless File.exist?(obj_dir)
42
+
43
+ obj_files.each do |path|
44
+ outfile = File.join(@outdir, path)
45
+ next if File.exist?(outfile)
46
+
47
+ parent = File.dirname(outfile)
48
+ Dir.mkdir(parent) unless File.exist?(parent)
49
+
50
+ STDERR << "Write: .git/plain/#{path}\n"
51
+ obj_file = File.join(@target, path)
52
+ File.open(obj_file) do |input|
53
+ File.open(outfile, "w") do |output|
54
+ dumper = GitObjectBrowser::ObjectDumper.new(input, output)
55
+ dumper.dump
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,77 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module GitObjectBrowser
4
+
5
+ class IndexDumper
6
+
7
+ def initialize(input, output)
8
+ @index = Models::Index.new(input)
9
+ @out = output
10
+ end
11
+
12
+ def dump
13
+ @out << "----------------------------------------------------------------\n"
14
+ @out << "header\n"
15
+ @out << "----------------------------------------------------------------\n"
16
+ @out << "version: #{@index.version}\n"
17
+ @out << "entries: #{@index.entry_count}\n"
18
+ @out << "\n"
19
+
20
+ @index.entries.each_with_index do |entry, i|
21
+ @out << "----------------------------------------------------------------\n"
22
+ @out << "entry: #{i+1}\n"
23
+ @out << "----------------------------------------------------------------\n"
24
+ @out << " ctime: #{entry.ctime}\n"
25
+ @out << " ctime nano: #{entry.cnano}\n"
26
+ @out << " mtime: #{entry.mtime}\n"
27
+ @out << " mtime nano: #{entry.mnano}\n"
28
+ @out << " dev: #{entry.dev}\n"
29
+ @out << " ino: #{entry.ino}\n"
30
+ @out << " object_type: #{entry.object_type}\n"
31
+ @out << " unix_permission: #{entry.unix_permission}\n"
32
+ @out << " uid: #{entry.uid}\n"
33
+ @out << " gid: #{entry.gid}\n"
34
+ @out << " size: #{entry.size}\n"
35
+ @out << " sha1: #{entry.sha1}\n"
36
+ if @version == 2
37
+ @out << "assume_valid_flag: #{entry.assume_valid_flag}\n"
38
+ @out << " extended_flag: #{entry.extended_flag}\n"
39
+ @out << " stage: #{entry.stage}\n"
40
+ elsif @version == 3
41
+ @out << " skip_worktree: #{entry.skip_worktree}\n"
42
+ @out << " intent_to_add: #{entry.intent_to_add}\n"
43
+ end
44
+ @out << " name_length: #{entry.name_length}\n"
45
+ @out << " path: #{entry.path}\n"
46
+ @out << "\n"
47
+ end
48
+
49
+ @index.extensions.each do |extension|
50
+ @out << "----------------------------------------------------------------\n"
51
+ @out << "extension: #{extension.signature}\n"
52
+ @out << "----------------------------------------------------------------\n"
53
+ if extension.signature == "TREE"
54
+ dump_tree_extension(extension)
55
+ elsif extension.signature == "REUC"
56
+ dump_reuc_extension
57
+ end
58
+ end
59
+
60
+ @out << "----------------------------------------------------------------\n"
61
+ @out << "checksum\n"
62
+ @out << "----------------------------------------------------------------\n"
63
+ @out << "sha1: #{@index.sha1}\n"
64
+ end
65
+
66
+ def dump_tree_extension(tree_extension)
67
+ tree_extension.entries.each do |entry|
68
+ @out << " path_component: #{entry[:path_component]}\n"
69
+ @out << " entry_count: #{entry[:entry_count]}\n"
70
+ @out << " subtree_count: #{entry[:subtree_count]}\n"
71
+ @out << " sha1: #{entry[:sha1]}\n\n"
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,63 @@
1
+ # -*- coding: utf-8 -*-
2
+ module GitObjectBrowser
3
+ class Main
4
+ def execute
5
+ host = '127.0.0.1'
6
+ port = 8080
7
+ opts = OptionParser.new do |opts|
8
+ opts.on('-p', '--port=PORT', 'port number') do |value|
9
+ port = value.to_i if 0 < value.to_i
10
+ end
11
+ opts.on('-b', '--bind=HOST', 'address to bind') do |value|
12
+ host = value
13
+ end
14
+ opts.on('-d', '--dump', 'dump objects') do
15
+ puts 'not implemented yet'
16
+ exit
17
+ end
18
+ opts.on_tail("-h", "--help", "show this help.") do
19
+ puts opts
20
+ exit
21
+ end
22
+ opts.on_tail("-v", "--version", "show version.") do
23
+ puts "git-object-browser " + VERSION
24
+ exit
25
+ end
26
+ opts.parse!(ARGV)
27
+ end
28
+
29
+ target = find_target(ARGV[0])
30
+ Server::Main.execute(target, host, port)
31
+ end
32
+
33
+ def find_target(target = nil, git_dir_name = '.git')
34
+ target ||= Dir::pwd
35
+ return target if git_dir?(target)
36
+
37
+ if File.directory?(target)
38
+ dir = parent_git_dir(target, git_dir_name)
39
+ return dir if dir
40
+ end
41
+
42
+ raise 'Git directory not found'
43
+ end
44
+
45
+ def git_dir?(dir)
46
+ return false unless File.directory?(dir)
47
+ return false unless File.directory?(File.join(dir, 'objects'))
48
+ return false unless File.file?(File.join(dir, 'HEAD'))
49
+ true
50
+ end
51
+
52
+ def parent_git_dir(target, git_dir_name)
53
+ begin
54
+ gitdir = File.join(target, git_dir_name)
55
+ return gitdir if git_dir?(gitdir)
56
+ lastdir = target
57
+ target = File.dirname(target)
58
+ end while lastdir != target
59
+ nil
60
+ end
61
+ private :parent_git_dir
62
+ end
63
+ end
@@ -0,0 +1,68 @@
1
+ module GitObjectBrowser
2
+
3
+ module Models
4
+
5
+ class Bindata
6
+
7
+ def initialize(input)
8
+ @in = input
9
+ end
10
+
11
+ def switch_source(input)
12
+ tmp = @in
13
+ @in = input
14
+ yield
15
+ ensure
16
+ @in = tmp
17
+ end
18
+
19
+ def raw(bytes)
20
+ @in.read(bytes)
21
+ end
22
+
23
+ def bytes(bytes)
24
+ @in.read(bytes).unpack('C*')
25
+ end
26
+
27
+ def byte
28
+ bytes(1).first
29
+ end
30
+
31
+ def int
32
+ @in.read(4).unpack('N').first.to_i
33
+ end
34
+
35
+ def hex(bytes)
36
+ @in.read(bytes).unpack('H*').first
37
+ end
38
+
39
+ def binstr(bytes)
40
+ @in.read(bytes).unpack('B*').first
41
+ end
42
+
43
+ def find_char(char)
44
+ buf = ''
45
+ loop do
46
+ c = @in.read(1)
47
+ return buf if c.nil? || c == char
48
+ buf += c
49
+ end
50
+ end
51
+
52
+ def skip(bytes)
53
+ @in.seek(bytes, IO::SEEK_CUR)
54
+ end
55
+
56
+ def seek(bytes)
57
+ @in.seek(bytes)
58
+ end
59
+
60
+ def peek(bytes)
61
+ result = raw(bytes)
62
+ @in.seek(bytes * -1, IO::SEEK_CUR)
63
+ result
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,61 @@
1
+
2
+ module GitObjectBrowser
3
+
4
+ module Models
5
+
6
+ class Directory
7
+ def initialize(root, path)
8
+ @root = root
9
+ @path = path
10
+ @entries = read_entries
11
+ end
12
+
13
+ def read_entries
14
+ entries = []
15
+ Dir.chdir(File.join(@root, @path)) do
16
+ files = Dir.glob("*")
17
+ files.each do |file|
18
+ relpath = File.join(@path, file).gsub(%r{\A/}, '')
19
+ entry = {}
20
+ if File.directory?(file)
21
+ entry[:type] = "directory"
22
+ elsif File.symlink?(file)
23
+ entry[:type] = "symlink"
24
+ elsif Ref::path?(relpath)
25
+ entry[:type] = 'ref'
26
+ elsif InfoRefs::path?(relpath)
27
+ entry[:type] = 'info_refs'
28
+ elsif PackedRefs::path?(relpath)
29
+ entry[:type] = 'packed_refs'
30
+ elsif Index::path?(relpath)
31
+ entry[:type] = 'index'
32
+ elsif GitObject::path?(relpath)
33
+ entry[:type] = 'object'
34
+ else
35
+ entry[:type] = "file"
36
+ end
37
+ entry[:basename] = file
38
+ entry[:mtime] = File.mtime(file).to_i
39
+ entry[:size] = File.size(file)
40
+ entries << entry
41
+ end
42
+ end
43
+ order = %w{directory ref info_refs packed_refs index object file symlink}
44
+ entries.sort do |a,b|
45
+ (order.index(a[:type]) <=> order.index(b[:type])).nonzero? ||
46
+ a[:basename] <=> b[:basename]
47
+ end
48
+ end
49
+
50
+ def to_hash
51
+ return {
52
+ "type" => "directory",
53
+ "path" => @path,
54
+ "entries" => @entries
55
+ }
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,127 @@
1
+ # -*- coding: utf-8 -*-
2
+ module GitObjectBrowser
3
+
4
+ module Models
5
+
6
+ class GitObject < Bindata
7
+
8
+ attr_reader :sha1, :type, :size, :entries, :contents
9
+ attr_reader :properties, :message
10
+
11
+ def initialize(input)
12
+ super(input)
13
+ end
14
+
15
+ def parse
16
+ content = Zlib::Inflate.inflate(@in.read(nil))
17
+ parse_inflated(content)
18
+ self
19
+ end
20
+
21
+ def parse_inflated(content)
22
+ @sha1 = Digest::SHA1.hexdigest(content)
23
+ @in = StringIO.new(content)
24
+
25
+ @type = find_char ' '
26
+ @size = find_char "\0"
27
+
28
+ @type = type
29
+ @size = size
30
+
31
+ if @type == 'tree'
32
+ @entries = parse_tree_entries
33
+ else
34
+ @content = @in.read(nil)
35
+ if @type == 'commit' or @type == 'tag'
36
+ (@properties, @message) = parse_contents
37
+ end
38
+
39
+ @content = @content.force_encoding('UTF-8')
40
+ @content = '(not UTF-8)' unless @content.valid_encoding?
41
+ @content = @content[0, 3000] + "\n..." if @content.length > 3000
42
+ end
43
+
44
+ self
45
+ end
46
+
47
+
48
+ def to_hash
49
+ return {
50
+ 'type' => @type,
51
+ 'sha1' => @sha1,
52
+ 'size' => @size,
53
+ 'entries' => @entries,
54
+ 'content' => @content,
55
+ 'properties' => @properties,
56
+ 'message' => @message,
57
+ }
58
+ end
59
+
60
+ def self.path?(relpath)
61
+ relpath =~ %r{\Aobjects/[0-9a-f]{2}/[0-9a-f]{38}\z}
62
+ end
63
+
64
+
65
+ private
66
+
67
+ def parse_tree_entries
68
+ entries = []
69
+ loop do
70
+ entry = {}
71
+ entry[:mode] = find_char ' '
72
+ break if entry[:mode].empty?
73
+ entry[:filename] = find_char "\0"
74
+ entry[:sha1] = hex(20)
75
+ entries << entry
76
+ end
77
+ return entries
78
+ end
79
+
80
+ def parse_contents
81
+ lines = @content.split /\n/
82
+ line = ''
83
+ properties = []
84
+ message = ''
85
+ while ! lines.empty?
86
+ line = lines.shift
87
+ break if line.empty?
88
+ prop = {}
89
+ (prop['key'], prop['value']) = line.split(/ /, 2)
90
+ if prop['value'] =~ /\A([0-9a-f]{2})([0-9a-f]{38})\z/
91
+ prop['type'] = 'sha1'
92
+ prop['path'] = "objects/#{ $1 }/#{ $2 }"
93
+ elsif %w{author committer tagger}.include?(prop['key']) &&
94
+ # couldn't find the spec...
95
+ prop['value'].to_s =~ /\A(.*) <(.*)> (\d+)(?: ((?:(?:\+|-)(?:\d{4}|\d{2}:\d{2}))|Z))?\z/
96
+ prop['type'] = 'user'
97
+ prop['name'] = $1.force_encoding("UTF-8")
98
+ prop['name'] = '(not UTF-8)' unless prop['name'].valid_encoding?
99
+ prop['email'] = $2.force_encoding("UTF-8")
100
+ prop['email'] = '(not UTF-8)' unless prop['email'].valid_encoding?
101
+ prop['unixtime'] = $3
102
+ prop['timezone'] = $4
103
+ prop['date'] = epoch($3.to_i, $4).iso8601
104
+ else
105
+ prop['type'] = 'text'
106
+ end
107
+ properties << prop
108
+ end
109
+ message = lines.join("\n").force_encoding("UTF-8")
110
+ message = '(not UTF-8)' unless message.valid_encoding?
111
+
112
+ [properties, message]
113
+ end
114
+
115
+ def epoch(sec, timezone)
116
+ DateTime.strptime(sec.to_s, '%s').new_offset(parse_timezone(timezone))
117
+ end
118
+
119
+ def parse_timezone(timezone)
120
+ timezone = '+00:00' if timezone == 'Z'
121
+ return Rational(0, 24) unless timezone =~ /(\+|-)?(\d\d):?(\d\d)/
122
+ Rational($2.to_i, 24) + Rational($3, 60) * (($1 == '-') ? -1 : 1)
123
+ end
124
+
125
+ end
126
+ end
127
+ end