git-object-browser 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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