git-object-browser 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +13 -0
- data/Guardfile +11 -0
- data/LICENSE +22 -0
- data/README.md +18 -0
- data/Rakefile +2 -0
- data/bin/git-object-browser +5 -0
- data/git-object-browser.gemspec +18 -0
- data/htdocs/css/angular-ui.min.css +1 -0
- data/htdocs/css/bootstrap-responsive.css +1058 -0
- data/htdocs/css/bootstrap-responsive.min.css +9 -0
- data/htdocs/css/bootstrap.css +5389 -0
- data/htdocs/css/bootstrap.min.css +699 -0
- data/htdocs/css/font-awesome-ie7.css +645 -0
- data/htdocs/css/font-awesome.css +303 -0
- data/htdocs/css/main.css +22 -0
- data/htdocs/font/fontawesome-webfont.eot +0 -0
- data/htdocs/font/fontawesome-webfont.svg +255 -0
- data/htdocs/font/fontawesome-webfont.ttf +0 -0
- data/htdocs/font/fontawesome-webfont.woff +0 -0
- data/htdocs/img/glyphicons-halflings-white.png +0 -0
- data/htdocs/img/glyphicons-halflings.png +0 -0
- data/htdocs/index.html +114 -0
- data/htdocs/js/main.js +286 -0
- data/htdocs/js/vendor/angular-bootstrap-prettify.min.js +41 -0
- data/htdocs/js/vendor/angular-bootstrap.min.js +9 -0
- data/htdocs/js/vendor/angular-cookies.min.js +7 -0
- data/htdocs/js/vendor/angular-loader.min.js +7 -0
- data/htdocs/js/vendor/angular-resource.min.js +10 -0
- data/htdocs/js/vendor/angular-sanitize.min.js +13 -0
- data/htdocs/js/vendor/angular-ui-ieshiv.min.js +7 -0
- data/htdocs/js/vendor/angular-ui.min.js +7 -0
- data/htdocs/js/vendor/angular.js +14401 -0
- data/htdocs/js/vendor/angular.min.js +158 -0
- data/htdocs/js/vendor/bootstrap.js +2027 -0
- data/htdocs/js/vendor/bootstrap.min.js +6 -0
- data/htdocs/js/vendor/html5shiv.js +5 -0
- data/htdocs/js/vendor/jquery-1.8.2.min.js +2 -0
- data/htdocs/templates/directory.html +25 -0
- data/htdocs/templates/file.html +6 -0
- data/htdocs/templates/git.html +1 -0
- data/htdocs/templates/index.html +44 -0
- data/htdocs/templates/index_entry.html +54 -0
- data/htdocs/templates/info_refs.html +27 -0
- data/htdocs/templates/loading.html +1 -0
- data/htdocs/templates/notfound.html +7 -0
- data/htdocs/templates/object.html +54 -0
- data/htdocs/templates/objects.html +16 -0
- data/htdocs/templates/pack_file.html +15 -0
- data/htdocs/templates/pack_index.html +38 -0
- data/htdocs/templates/packed_object.html +111 -0
- data/htdocs/templates/packed_refs.html +27 -0
- data/htdocs/templates/ref.html +12 -0
- data/lib/git-object-browser.rb +37 -0
- data/lib/git-object-browser/dumper.rb +62 -0
- data/lib/git-object-browser/index_dumper.rb +77 -0
- data/lib/git-object-browser/main.rb +63 -0
- data/lib/git-object-browser/models/bindata.rb +68 -0
- data/lib/git-object-browser/models/directory.rb +61 -0
- data/lib/git-object-browser/models/git_object.rb +127 -0
- data/lib/git-object-browser/models/index.rb +77 -0
- data/lib/git-object-browser/models/index_entry.rb +95 -0
- data/lib/git-object-browser/models/index_reuc_extension.rb +31 -0
- data/lib/git-object-browser/models/index_tree_extension.rb +52 -0
- data/lib/git-object-browser/models/info_refs.rb +32 -0
- data/lib/git-object-browser/models/pack_file.rb +40 -0
- data/lib/git-object-browser/models/pack_index.rb +146 -0
- data/lib/git-object-browser/models/packed_object.rb +239 -0
- data/lib/git-object-browser/models/packed_refs.rb +39 -0
- data/lib/git-object-browser/models/plain_file.rb +24 -0
- data/lib/git-object-browser/models/ref.rb +32 -0
- data/lib/git-object-browser/object_dumper.rb +31 -0
- data/lib/git-object-browser/server/git_servlet.rb +209 -0
- data/lib/git-object-browser/server/main.rb +31 -0
- data/lib/git-object-browser/version.rb +3 -0
- data/spec/fixtures/generate_worktree.sh +103 -0
- data/spec/fixtures/git/indexes/001 +0 -0
- data/spec/fixtures/git/indexes/002-empty-tree-extension +0 -0
- data/spec/fixtures/git/plain_file +1 -0
- data/spec/fixtures/json/blob.json +9 -0
- data/spec/fixtures/json/merge-a.json +48 -0
- data/spec/fixtures/json/test3-tag.json +36 -0
- data/spec/fixtures/json/tree.json +20 -0
- data/spec/fixtures/worktree/_git/COMMIT_EDITMSG +1 -0
- data/spec/fixtures/worktree/_git/HEAD +1 -0
- data/spec/fixtures/worktree/_git/ORIG_HEAD +1 -0
- data/spec/fixtures/worktree/_git/config +11 -0
- data/spec/fixtures/worktree/_git/description +1 -0
- data/spec/fixtures/worktree/_git/hooks/applypatch-msg.sample +15 -0
- data/spec/fixtures/worktree/_git/hooks/commit-msg.sample +24 -0
- data/spec/fixtures/worktree/_git/hooks/post-update.sample +8 -0
- data/spec/fixtures/worktree/_git/hooks/pre-applypatch.sample +14 -0
- data/spec/fixtures/worktree/_git/hooks/pre-commit.sample +50 -0
- data/spec/fixtures/worktree/_git/hooks/pre-rebase.sample +169 -0
- data/spec/fixtures/worktree/_git/hooks/prepare-commit-msg.sample +36 -0
- data/spec/fixtures/worktree/_git/hooks/update.sample +128 -0
- data/spec/fixtures/worktree/_git/ignore +3 -0
- data/spec/fixtures/worktree/_git/index +0 -0
- data/spec/fixtures/worktree/_git/info/exclude +6 -0
- data/spec/fixtures/worktree/_git/info/refs +1 -0
- data/spec/fixtures/worktree/_git/logs/HEAD +10 -0
- data/spec/fixtures/worktree/_git/logs/refs/heads/branch-a +3 -0
- data/spec/fixtures/worktree/_git/logs/refs/heads/branch-b +2 -0
- data/spec/fixtures/worktree/_git/logs/refs/heads/master +4 -0
- data/spec/fixtures/worktree/_git/objects/00/cb8bfeb5b8ce906d39698e4e33b38341f5448f +1 -0
- data/spec/fixtures/worktree/_git/objects/07/31f9d4b6fa0475872be6a8ca263096f1d201cf +2 -0
- data/spec/fixtures/worktree/_git/objects/1d/3dc60b5a117054e43741d51e599ff31bb15f9f +0 -0
- data/spec/fixtures/worktree/_git/objects/26/4e42b1fef5bcb55acec162fdd5a068d79ae551 +0 -0
- data/spec/fixtures/worktree/_git/objects/28/3c06ddf1b31c14bb221d41173299e133b7753d +0 -0
- data/spec/fixtures/worktree/_git/objects/37/d1632d3f1159dad9cfb58e6c34312ab4355c49 +0 -0
- data/spec/fixtures/worktree/_git/objects/3a/2bf444f105c19b13ba5e75e884e10715e95a91 +0 -0
- data/spec/fixtures/worktree/_git/objects/40/a061aaf0cd0555449671a11993e4fed11f91a4 +2 -0
- data/spec/fixtures/worktree/_git/objects/53/2a1874c26cd19bd4d66e03218ab73e63de4357 +3 -0
- data/spec/fixtures/worktree/_git/objects/5b/719b165fde1964fb5a08adaf3b6e4f57ca1ff5 +0 -0
- data/spec/fixtures/worktree/_git/objects/61/cae34206bb889bae43ffdd22c17217485178bf +0 -0
- data/spec/fixtures/worktree/_git/objects/6c/444ac15f1e3c2a6869bd36ca7e58c39512106f +2 -0
- data/spec/fixtures/worktree/_git/objects/93/b714995d24c52180195876058a49c7d7fea0ad +0 -0
- data/spec/fixtures/worktree/_git/objects/96/25401ac3e19ef10868c140a76b719ac3f08fcf +0 -0
- data/spec/fixtures/worktree/_git/objects/b9/29ed2ab14f7489d5238a06d10d2f2c229a4ab4 +0 -0
- data/spec/fixtures/worktree/_git/objects/be/e0d26d33c284ee065e38bd7e81ae4bdc870f89 +4 -0
- data/spec/fixtures/worktree/_git/objects/c3/6491256978d26c08cd7aa97eee0f5631f96659 +0 -0
- data/spec/fixtures/worktree/_git/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 +0 -0
- data/spec/fixtures/worktree/_git/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b +0 -0
- data/spec/fixtures/worktree/_git/objects/e5/b6d4317cefa946d77fc91539f1f1e48b60836f +0 -0
- data/spec/fixtures/worktree/_git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
- data/spec/fixtures/worktree/_git/objects/info/packs +2 -0
- data/spec/fixtures/worktree/_git/objects/pack/pack-f1c1717e9264c12310c7bd3e7dcdd28924000ff6.idx +0 -0
- data/spec/fixtures/worktree/_git/objects/pack/pack-f1c1717e9264c12310c7bd3e7dcdd28924000ff6.pack +0 -0
- data/spec/fixtures/worktree/_git/packed-refs +2 -0
- data/spec/fixtures/worktree/_git/refs/heads/branch-a +1 -0
- data/spec/fixtures/worktree/_git/refs/heads/branch-b +1 -0
- data/spec/fixtures/worktree/_git/refs/heads/master +1 -0
- data/spec/fixtures/worktree/_git/refs/tags/simple-tag +1 -0
- data/spec/fixtures/worktree/_git/refs/tags/test3-tag +1 -0
- data/spec/fixtures/worktree/sample-a.txt +1 -0
- data/spec/fixtures/worktree/sample.txt +1 -0
- data/spec/fixtures/worktree/subdir/sample-sub.txt +0 -0
- data/spec/git-object-browser/main_spec.rb +31 -0
- data/spec/git-object-browser/models/bindata_spec.rb +144 -0
- data/spec/git-object-browser/models/git_object_spec.rb +49 -0
- data/spec/git-object-browser/models/index_spec.rb +42 -0
- data/spec/git-object-browser/models/index_tree_extension_spec.rb +58 -0
- data/spec/git-object-browser/models/plain_file_spec.rb +11 -0
- data/spec/spec_helper.rb +19 -0
- 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
|