mojombo-grit 0.8.1 → 0.9.3

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 (59) hide show
  1. data/History.txt +7 -0
  2. data/Manifest.txt +18 -1
  3. data/grit.gemspec +56 -10
  4. data/lib/{mojombo-grit.rb → grit.rb} +20 -4
  5. data/lib/grit/commit.rb +32 -11
  6. data/lib/grit/commit_stats.rb +104 -0
  7. data/lib/grit/git-ruby.rb +182 -0
  8. data/lib/grit/git-ruby/commit_db.rb +52 -0
  9. data/lib/grit/git-ruby/file_index.rb +186 -0
  10. data/lib/grit/git-ruby/git_object.rb +344 -0
  11. data/lib/grit/git-ruby/internal/loose.rb +136 -0
  12. data/lib/grit/git-ruby/internal/mmap.rb +58 -0
  13. data/lib/grit/git-ruby/internal/pack.rb +382 -0
  14. data/lib/grit/git-ruby/internal/raw_object.rb +37 -0
  15. data/lib/grit/git-ruby/object.rb +319 -0
  16. data/lib/grit/git-ruby/repository.rb +729 -0
  17. data/lib/grit/git.rb +33 -15
  18. data/lib/grit/head.rb +6 -15
  19. data/lib/grit/index.rb +121 -0
  20. data/lib/grit/ref.rb +95 -0
  21. data/lib/grit/repo.rb +95 -6
  22. data/lib/grit/status.rb +151 -0
  23. data/lib/grit/tree.rb +3 -2
  24. data/test/test_blob.rb +5 -0
  25. data/test/test_commit.rb +7 -5
  26. data/test/test_diff.rb +1 -1
  27. data/test/test_git.rb +20 -2
  28. data/test/test_grit.rb +32 -0
  29. data/test/test_head.rb +30 -5
  30. data/test/test_real.rb +8 -6
  31. data/test/test_remote.rb +14 -0
  32. data/test/test_repo.rb +86 -79
  33. data/test/test_tag.rb +2 -6
  34. data/test/test_tree.rb +5 -0
  35. metadata +40 -40
  36. data/test/fixtures/blame +0 -131
  37. data/test/fixtures/cat_file_blob +0 -1
  38. data/test/fixtures/cat_file_blob_size +0 -1
  39. data/test/fixtures/diff_2 +0 -54
  40. data/test/fixtures/diff_2f +0 -19
  41. data/test/fixtures/diff_f +0 -15
  42. data/test/fixtures/diff_i +0 -201
  43. data/test/fixtures/diff_mode_only +0 -1152
  44. data/test/fixtures/diff_new_mode +0 -17
  45. data/test/fixtures/diff_p +0 -610
  46. data/test/fixtures/for_each_ref +0 -0
  47. data/test/fixtures/for_each_ref_tags +0 -0
  48. data/test/fixtures/ls_tree_a +0 -7
  49. data/test/fixtures/ls_tree_b +0 -2
  50. data/test/fixtures/ls_tree_commit +0 -3
  51. data/test/fixtures/rev_list +0 -26
  52. data/test/fixtures/rev_list_count +0 -655
  53. data/test/fixtures/rev_list_single +0 -7
  54. data/test/fixtures/rev_parse +0 -1
  55. data/test/fixtures/show_empty_commit +0 -6
  56. data/test/fixtures/simple_config +0 -2
  57. data/test/helper.rb +0 -17
  58. data/test/profile.rb +0 -21
  59. data/test/suite.rb +0 -6
@@ -0,0 +1,52 @@
1
+ begin
2
+ require 'sequel'
3
+
4
+ module Grit
5
+
6
+ class CommitDb
7
+
8
+ SCHEMA_VERSION = 1
9
+
10
+ attr_accessor :db, :git
11
+
12
+ def initialize(git_obj, index_location = nil)
13
+ @git = git_obj
14
+ db_file = File.join(index_location || @git.git_dir, 'commit_db')
15
+ if !File.exists?(db_file)
16
+ @db = Sequel.open "sqlite:///#{db_file}"
17
+ setup_tables
18
+ else
19
+ @db = Sequel.open "sqlite:///#{db_file}"
20
+ end
21
+ end
22
+
23
+ def rev_list(branch, options)
24
+ end
25
+
26
+ def update_db(branch = nil)
27
+ # find all refs/heads, for each
28
+ # add branch if not there
29
+ # go though all commits in branch
30
+ # add new commit_branches a
31
+ # and commit_nodes for each new one
32
+ # stop if reach commit that already has branch and node links
33
+ end
34
+
35
+ def setup_tables
36
+ @db << "create table meta (meta_key text, meta_value text)"
37
+ @db[:meta] << {:meta_key => 'schema', :meta_value => SCHEMA_VERSION}
38
+
39
+ @db << "create table commits (id integer, sha text, author_date integer)"
40
+ @db << "create table nodes (id integer, path text, type text)"
41
+ @db << "create table branches (id integer, ref text, commit_id integer)"
42
+
43
+ @db << "create table commit_branches (commit_id integer, branch_id integer)"
44
+ @db << "create table commit_nodes (commit_id integer, node_id integer, node_sha string)"
45
+ end
46
+
47
+ end
48
+ end
49
+
50
+ rescue LoadError
51
+ # no commit db
52
+ end
@@ -0,0 +1,186 @@
1
+ # this implements a file-based 'file index', an simple index of
2
+ # all of the reachable commits in a repo, along with the parents
3
+ # and which files were modified during each commit
4
+ #
5
+ # this class looks for a file named '[.git]/file-index', generated via:
6
+ #
7
+ # git log --pretty=oneline --name-only --parents --reverse --all > file-index
8
+ #
9
+ # for this to work properly, you'll want to add the following as a post-receive hook
10
+ # to keep the index up to date
11
+ #
12
+ # git log --pretty=oneline --name-only --parents --reverse [old-rev]..[new-rev] >> file-index
13
+ #
14
+ module Grit
15
+ module GitRuby
16
+
17
+ class FileIndex
18
+
19
+ class IndexFileNotFound < StandardError
20
+ end
21
+
22
+ class UnsupportedRef < StandardError
23
+ end
24
+
25
+ attr_reader :files
26
+
27
+ # initializes index given repo_path
28
+ def initialize(repo_path)
29
+ @index_file = File.join(repo_path, 'file-index')
30
+ if File.file?(@index_file)
31
+ read_index
32
+ else
33
+ raise IndexFileNotFound
34
+ end
35
+ end
36
+
37
+ # returns count of all commits
38
+ def count_all
39
+ @sha_count
40
+ end
41
+
42
+ # returns count of all commits reachable from SHA
43
+ # note: originally did this recursively, but ruby gets pissed about that on
44
+ # really big repos where the stack level gets 'too deep' (thats what she said)
45
+ def count(commit_sha)
46
+ commits_from(commit_sha).size
47
+ end
48
+
49
+ def commits_from(commit_sha)
50
+ raise UnsupportedRef if commit_sha.is_a? Array
51
+
52
+ already = {}
53
+ final = []
54
+ left_to_do = [commit_sha]
55
+
56
+ while commit_sha = left_to_do.shift
57
+ next if already[commit_sha]
58
+
59
+ final << commit_sha
60
+ already[commit_sha] = true
61
+
62
+ commit = @commit_index[commit_sha]
63
+ commit[:parents].each do |sha|
64
+ left_to_do << sha
65
+ end if commit
66
+ end
67
+
68
+ sort_commits(final)
69
+ end
70
+
71
+ def sort_commits(sha_array)
72
+ sha_array.sort { |a, b| @commit_order[b] <=> @commit_order[a] }
73
+ end
74
+
75
+ # returns files changed at commit sha
76
+ def files(commit_sha)
77
+ @commit_index[commit_sha][:files] rescue nil
78
+ end
79
+
80
+ # returns all commits for a file
81
+ def commits_for(file)
82
+ @all_files[file]
83
+ end
84
+
85
+ # returns the shas of the last commits for all
86
+ # the files in [] from commit_sha
87
+ # files_matcher can be a regexp or an array
88
+ def last_commits(commit_sha, files_matcher)
89
+ acceptable = commits_from(commit_sha)
90
+
91
+ matches = {}
92
+
93
+ if files_matcher.is_a? Regexp
94
+ files = @all_files.keys.select { |file| file =~ files_matcher }
95
+ files_matcher = files
96
+ end
97
+
98
+ if files_matcher.is_a? Array
99
+ # find the last commit for each file in the array
100
+ files_matcher.each do |f|
101
+ @all_files[f].each do |try|
102
+ if acceptable.include?(try)
103
+ matches[f] = try
104
+ break
105
+ end
106
+ end if @all_files[f]
107
+ end
108
+ end
109
+
110
+ matches
111
+ end
112
+
113
+ private
114
+
115
+ # read and parse the file-index data
116
+ def read_index
117
+ f = File.new(@index_file)
118
+ @sha_count = 0
119
+ @commit_index = {}
120
+ @commit_order = {}
121
+ @all_files = {}
122
+ while line = f.gets
123
+ if /^(\w{40})/.match(line)
124
+ shas = line.scan(/(\w{40})/)
125
+ current_sha = shas.shift.first
126
+ parents = shas.map { |sha| sha.first }
127
+ @commit_index[current_sha] = {:files => [], :parents => parents }
128
+ @commit_order[current_sha] = @sha_count
129
+ @sha_count += 1
130
+ else
131
+ file_name = line.chomp
132
+ tree = ''
133
+ File.dirname(file_name).split('/').each do |part|
134
+ next if part == '.'
135
+ tree += part + '/'
136
+ @all_files[tree] ||= []
137
+ @all_files[tree].unshift(current_sha)
138
+ end
139
+ @all_files[file_name] ||= []
140
+ @all_files[file_name].unshift(current_sha)
141
+ @commit_index[current_sha][:files] << file_name
142
+ end
143
+ end
144
+ end
145
+
146
+ end
147
+
148
+ end
149
+ end
150
+
151
+
152
+ # benchmark testing on big-ass repos
153
+
154
+ if __FILE__ == $0
155
+
156
+ #repo = '/Users/schacon/projects/git/.git'
157
+ #commit = 'd8933f013a66cc1deadf83a9c24eccb6fee78a35'
158
+ #file_list = ["builtin-merge-recursive.c", "git-send-email-script", "git-parse-remote.sh", "builtin-add.c", "merge-base.c", "read-cache.c", "fsck.h", "diff.c", "refs.c", "diffcore-rename.c", "epoch.c", "pack-intersect.c", "fast-import.c", "git-applypatch.sh", "git.spec.in", "rpush.c", "git-clone-script", "utf8.c", "git-external-diff-script", "builtin-init-db.c", "pack-redundant.c", "builtin-diff-index.c", "index.c", "update-index.c", "fetch-clone.c", "pager.c", "diff.h", "unpack-trees.c", "git-browse-help.sh", "git-rename-script", "refs.h", "get-tar-commit-id.c", "git-push.sh", "README", "delta.c", "mailsplit.c", "gitweb.cgi", "var.c", "epoch.h", "gsimm.c", "archive.c", "sideband.c", "utf8.h", "local-fetch.c", "git-request-pull-script", "builtin-send-pack.c", "blob.c", "builtin-ls-remote.c", "pretty.c", "git-diff.sh", "diffcore-break.c", "unpack-trees.h", "git-mv.perl", "interpolate.c", "builtin-diff-files.c", "delta.h", "commit-tree.c", "git-diff-script", "decorate.c", "builtin-name-rev.c", "tree-walk.c", "git-revert-script", "git-sh-setup.sh", "transport.c", "gsimm.h", "archive.h", "count-delta.c", "sideband.h", "git-merge.sh", "git-gui.sh", "git-core.spec.in", "cvs2git.c", "blob.h", "git.sh", "http-push.c", "builtin-commit-tree.c", "diff-helper.c", "builtin-push.c", "interpolate.h", "decorate.h", "git-citool", "dotest", "builtin-verify-tag.c", "git-mergetool.sh", "tree-walk.h", "log-tree.c", "name-rev.c", "applypatch", "cat-file.c", "test-delta.c", "server-info.c", "count-delta.h", "write-tree.c", "local-pull.c", "transport.h", "git-rm.sh", "unpack-objects.c", "xdiff-interface.c", "git-repack-script", "commit.c", "hash-object.c", "git-merge-recursive.py", "git-clone-dumb-http", "thread-utils.c", "git-send-email.perl", "git-whatchanged.sh", "log-tree.h", "builtin-annotate.c", "show-index.c", "pkt-line.c", "ident.c", "git-rebase-script", "name-hash.c", "git-archimport.perl", "xdiff-interface.h", "commit.h", "diff-lib.c", "wt-status.c", "base85.c", "builtin-fetch--tool.c", "unpack-file.c", "builtin-diff-stages.c", "merge-index.c", "color.c", "diff-tree.c", "git-checkout.sh", "thread-utils.h", "grep.c", "pkt-line.h", "builtin-help.c", "test-parse-options.c", "show-files.c", "git.sh.in", "pack.h", "wt-status.h", "git-prune-script", "test-sha1.c", "git-octopus.sh", "dump-cache-tree.c", "git-web--browse.sh", "builtin-upload-tar.c", "builtin-clone.c", "copy.c", "color.h", "show-branch.c", "peek-remote.c", "git-merge-recursive-old.py", "cmd-rename.sh", "git-apply-patch-script", "git-export.c", "git-relink-script", "grep.h", "usage.c", "git-fetch-dumb-http", "fsck-objects.c", "update-cache.c", "diff-stages.c", "patch-ids.c", "builtin-rev-list.c", "builtin-bundle.c", "builtin-show-branch.c", "builtin-pack-refs.c", "tree.c", "git.c", "verify_pack.c", "update-ref.c", "builtin-peek-remote.c", "diffcore-pathspec.c", "git-merge-octopus.sh", "git-show-branches-script", "builtin-archive.c", "builtin-unpack-objects.c", "git-rerere.perl", "walker.c", "builtin-mailsplit.c", "convert.c", "builtin-branch.c", "export.c", "patch-ids.h", "check-builtins.sh", "git-pull-script", "tree.h", "alloc.c", "git-commit.sh", "git-lost-found.sh", "mailmap.c", "rsh.c", "exec_cmd.c", "git-compat-util.h", "ws.c", "rev-list.c", "git-verify-tag.sh", "git-ls-remote-script", "mktree.c", "walker.h", "builtin-blame.c", "builtin-fsck.c", "setup.c", "git-cvsimport-script", "git-add.sh", "symlinks.c", "checkout-index.c", "receive-pack.c", "git-merge-one-file-script", "mailmap.h", "git-cvsimport.perl", "builtin-count.c", "exec_cmd.h", "builtin-stripspace.c", "git-grep.sh", "hash.c", "builtin-prune-packed.c", "git-rebase--interactive.sh", "rsh.h", "match-trees.c", "git-format-patch.sh", "git-push-script", "parse-options.c", "git-status-script", "http-walker.c", "pack-write.c", "git-status.sh", "diff-delta.c", "hash.h", "generate-cmdlist.sh", "config-set.c", "builtin-fetch.c", "ll-merge.c", "t1300-config-set.sh", "ls-tree.c", "write_or_die.c", "builtin-check-ref-format.c", "fetch-pack.c", "git-commit-script", "builtin-describe.c", "parse-options.h", "builtin-checkout.c", "prune-packed.c", "fixup-builtins", "http-fetch.c", "test-absolute-path.c", "git-log.sh", "builtin-merge-ours.c", "git-whatchanged", "pull.c", "merge-tree.c", "ll-merge.h", "builtin.h", "Makefile", "cache-tree.c", "builtin-log.c", "merge-cache.c", "fetch-pack.h", "git-shortlog.perl", "git-bisect-script", "git-am.sh", "check-ref-format.c", "git-count-objects-script", "mkdelta.c", "builtin-diff.c", "merge-recursive.c", "builtin-config.c", "gitenv.c", "describe.c", "git-add--interactive.perl", "pull.h", "builtin-apply.c", "diff-index.c", "ssh-pull.c", "builtin-merge-file.c", "strbuf.c", "git-submodule.sh", "repo-config.c", "run-command.c", "git-applymbox.sh", "cache-tree.h", "builtin-clean.c", "cache.h", "git-prune.sh", "fsck-cache.c", "builtin-remote.c", "sha1_file.c", "shallow.c", "merge-recursive.h", "builtin-checkout-index.c", "git-clone.sh", "builtin-mv.c", "builtin-reflog.c", "lockfile.c", "git-octopus-script", ".mailmap", "strbuf.h", "git-p4import.py", "builtin-repo-config.c", "patch-delta.c", "builtin-merge-base.c", "run-command.h", "check-racy.c", "git-filter-branch.sh", "git-branch.sh", "git-merge-stupid.sh", "diff-files.c", "test-sha1.sh", "COPYING", "git-lost+found.sh", "git-tag.sh", "git-branch-script", "check-files.c", "builtin-reset.c", "builtin-ls-files.c", "builtin-fmt-merge-msg.c", "builtin-for-each-ref.c", "csum-file.c", "git-gc.sh", "git-parse-remote-script", "command-list.txt", "builtin-pack-objects.c", "dir.c", "test-date.c", "builtin-grep.c", "list-objects.c", "clone-pack.c", "git-gui", "convert-cache.c", "git-reset-script", "checkout-cache.c", "git-ls-remote.sh", "read-tree.c", "git-instaweb.sh", "progress.c", "rabinpoly.c", "ls-files.c", "mktag.c", "gitMergeCommon.py", "git-merge-ours.sh", "rpull.c", "git-annotate.perl", "csum-file.h", "builtin-shortlog.c", "builtin-commit.c", "http-pull.c", "git-fetch.sh", "apply.c", "git-add-script", "dir.h", "diff-tree-helper.c", "list-objects.h", "rev-tree.c", "builtin-tar-tree.c", "progress.h", "builtin-pickaxe.c", "git-merge-fredrik.py", "path.c", "builtin-diff-tree.c", "rabinpoly.h", "builtin-ls-tree.c", "tar.h", "trace.c", "graph.c", "ssh-fetch.c", "show-diff.c", "sha1-lookup.c", "builtin-revert.c", "builtin-symbolic-ref.c", "builtin-write-tree.c", "git-sh-setup-script", "rev-cache.c", "blame.c", "builtin-mailinfo.c", "git-cherry", "git-resolve-script", "INSTALL", "git-findtags.perl", "diffcore-delta.c", "entry.c", "git-applypatch", "connect.c", "tar-tree.c", "graph.h", "missing-revs.c", "builtin-fast-export.c", "sha1-lookup.h", "rev-parse.c", "configure.ac", "rev-cache.h", "build-rev-cache.c", "reachable.c", "index-pack.c", "git", "send-pack.c", "git-cherry.sh", "git-tag-script", "revision.c", "CREDITS-GEN", "bundle.c", "mailinfo.c", "symbolic-ref.c", "attr.c", "git-archimport-script", "archive-zip.c", "diff-cache.c", "fetch.c", "builtin-gc.c", "git-remote.perl", "path-list.c", "ssh-upload.c", "reachable.h", "diff-no-index.c", "diffcore.h", "send-pack.h", "tree-diff.c", "git-checkout-script", "pack-revindex.c", "show-rev-cache.c", "TODO", "revision.h", "bundle.h", "unresolve.c", "git-deltafy-script", "git-relink.perl", "archive-tar.c", "attr.h", "git-resolve.sh", "config.mak.in", "builtin-update-index.c", "convert-objects.c", "fetch.h", "builtin-runstatus.c", "quote.c", "init-db.c", "git-shortlog", "builtin-prune.c", "builtin-rerere.c", "verify-pack.c", "gitk", "patch-id.c", ".gitattributes", "date.c", "git-format-patch-script", "path-list.h", "pack-revindex.h", "GIT-VERSION-GEN", "combine-diff.c", "environment.c", "git-cvsserver.perl", "git-repack.sh", "diffcore-order.c", "reflog-walk.c", "config.c", "test-match-trees.c", "git-svnimport.perl", "quote.h", "write-blob.c", "diffcore-pickaxe.c", "builtin-update-ref.c", "stripspace.c", "help.c", "pack-objects.c", "branch.c", "git-verify-tag-script", "TEST", "daemon.c", "remote.c", "git-log-script", "git-pull.sh", "git-quiltimport.sh", "git-count-objects.sh", "reflog-walk.h", "git-applymbox", "builtin-show-ref.c", "RelNotes", "git-fmt-merge-msg.perl", "git-rebase.sh", "git-parse-remote", "git-browse--help.sh", "git-stash.sh", "alias.c", "branch.h", "gitweb.pl", "builtin-upload-archive.c", "builtin-cat-file.c", "sha1_name.c", "http.c", "test-chmtime.c", "remote.h", "ssh-push.c", "tag.c", "update-server-info.c", "git-cvsexportcommit.perl", "builtin-check-attr.c", "git-revert.sh", "builtin-verify-pack.c", "object.c", "git-merge-resolve.sh", "shortlog.h", "git-fetch-script", "test-genrandom.c", "shell.c", "builtin-rm.c", "builtin-zip-tree.c", "upload-pack.c", "git-rename.perl", ".gitignore", "tag.h", "http.h", "git-request-pull.sh", "object.h", "git-svn.perl", "builtin-fetch-pack.c", "git-bisect.sh", "pack-check.c", "builtin-rev-parse.c", "object-refs.c", "test-gsimm.c", "builtin-read-tree.c", "git-help--browse.sh", "merge-file.c", "fsck.c", "builtin-tag.c", "builtin-http-fetch.c", "builtin-count-objects.c", "git-reset.sh", "git-clean.sh", "git-merge-one-file.sh", "ctype.c", "git-mktag.c", "imap-send.c"]
159
+
160
+ repo = '/Users/schacon/projects/grit/.git'
161
+ commit = 'c87612bc84c95ba9df17674d911dde10f34fefaa'
162
+
163
+ require 'benchmark'
164
+
165
+ Benchmark.bm(20) do |x|
166
+ x.report('index build') do
167
+ i = Grit::GitRuby::FileIndex.new(repo)
168
+ end
169
+ x.report('commit count') do
170
+ i = Grit::GitRuby::FileIndex.new(repo)
171
+ i.count(commit)
172
+ end
173
+ x.report('commits list') do
174
+ i = Grit::GitRuby::FileIndex.new(repo)
175
+ i.commits_from(commit)
176
+ end
177
+ x.report('last commits') do
178
+ i = Grit::GitRuby::FileIndex.new(repo)
179
+ #arr = i.last_commits(commit, file_list)
180
+ arr = i.last_commits(commit, /^[^\/]*$/)
181
+ end
182
+ end
183
+ end
184
+
185
+
186
+
@@ -0,0 +1,344 @@
1
+ #
2
+ # converted from the gitrb project
3
+ #
4
+ # authors:
5
+ # Matthias Lederhofer <matled@gmx.net>
6
+ # Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
7
+ # Scott Chacon <schacon@gmail.com>
8
+ #
9
+ # provides native ruby access to git objects and pack files
10
+ #
11
+
12
+ # These classes translate the raw binary data kept in the sha encoded files
13
+ # into parsed data that can then be used in another fashion
14
+ require 'stringio'
15
+
16
+ module Grit
17
+ module GitRuby
18
+
19
+ # class for author/committer/tagger lines
20
+ class UserInfo
21
+ attr_accessor :name, :email, :date, :offset
22
+
23
+ def initialize(str)
24
+ @email = ''
25
+ @date = Time.now
26
+ @offset = 0
27
+
28
+ m = /^(.*?) <(.*)> (\d+) ([+-])0*(\d+?)$/.match(str)
29
+ if !m
30
+ case str
31
+ when /<.+>/
32
+ m, @name, @email = *str.match(/(.*) <(.+?)>/)
33
+ else
34
+ @name = str
35
+ end
36
+ else
37
+ @name = m[1]
38
+ @email = m[2]
39
+ @date = Time.at(Integer(m[3]))
40
+ @offset = (m[4] == "-" ? -1 : 1)*Integer(m[5])
41
+ end
42
+ end
43
+
44
+ def to_s
45
+ "%s <%s> %s %+05d" % [@name, @email, @date.to_i, @offset]
46
+ end
47
+ end
48
+
49
+ # base class for all git objects (blob, tree, commit, tag)
50
+ class GitObject
51
+ attr_accessor :repository
52
+
53
+ def GitObject.from_raw(rawobject, repository = nil)
54
+ case rawobject.type
55
+ when :blob
56
+ return Blob.from_raw(rawobject, repository)
57
+ when :tree
58
+ return Tree.from_raw(rawobject, repository)
59
+ when :commit
60
+ return Commit.from_raw(rawobject, repository)
61
+ when :tag
62
+ return Tag.from_raw(rawobject, repository)
63
+ else
64
+ raise RuntimeError, "got invalid object-type"
65
+ end
66
+ end
67
+
68
+ def initialize
69
+ raise NotImplemented, "abstract class"
70
+ end
71
+
72
+ def type
73
+ raise NotImplemented, "abstract class"
74
+ end
75
+
76
+ def raw_content
77
+ raise NotImplemented, "abstract class"
78
+ end
79
+
80
+ def sha1
81
+ Digest::SHA1.hexdigest("%s %d\0" % \
82
+ [self.type, self.raw_content.length] + \
83
+ self.raw_content)
84
+ end
85
+ end
86
+
87
+ class Blob < GitObject
88
+ attr_accessor :content
89
+
90
+ def self.from_raw(rawobject, repository)
91
+ new(rawobject.content)
92
+ end
93
+
94
+ def initialize(content, repository=nil)
95
+ @content = content
96
+ @repository = repository
97
+ end
98
+
99
+ def type
100
+ :blob
101
+ end
102
+
103
+ def raw_content
104
+ @content
105
+ end
106
+ end
107
+
108
+ class DirectoryEntry
109
+ S_IFMT = 00170000
110
+ S_IFLNK = 0120000
111
+ S_IFREG = 0100000
112
+ S_IFDIR = 0040000
113
+ S_IFGITLINK = 0160000
114
+ attr_accessor :mode, :name, :sha1
115
+ def initialize(mode, filename, sha1o)
116
+ @mode = 0
117
+ mode.each_byte do |i|
118
+ @mode = (@mode << 3) | (i-'0'[0])
119
+ end
120
+ @name = filename
121
+ @sha1 = sha1o
122
+ if ![S_IFLNK, S_IFDIR, S_IFREG, S_IFGITLINK].include?(@mode & S_IFMT)
123
+ raise RuntimeError, "unknown type for directory entry"
124
+ end
125
+ end
126
+
127
+ def type
128
+ case @mode & S_IFMT
129
+ when S_IFGITLINK
130
+ @type = :submodule
131
+ when S_IFLNK
132
+ @type = :link
133
+ when S_IFDIR
134
+ @type = :directory
135
+ when S_IFREG
136
+ @type = :file
137
+ else
138
+ raise RuntimeError, "unknown type for directory entry"
139
+ end
140
+ end
141
+
142
+ def type=(type)
143
+ case @type
144
+ when :link
145
+ @mode = (@mode & ~S_IFMT) | S_IFLNK
146
+ when :directory
147
+ @mode = (@mode & ~S_IFMT) | S_IFDIR
148
+ when :file
149
+ @mode = (@mode & ~S_IFMT) | S_IFREG
150
+ when :submodule
151
+ @mode = (@mode & ~S_IFMT) | S_IFGITLINK
152
+ else
153
+ raise RuntimeError, "invalid type"
154
+ end
155
+ end
156
+
157
+ def format_type
158
+ case type
159
+ when :link
160
+ 'link'
161
+ when :directory
162
+ 'tree'
163
+ when :file
164
+ 'blob'
165
+ when :submodule
166
+ 'commit'
167
+ end
168
+ end
169
+
170
+ def format_mode
171
+ "%06o" % @mode
172
+ end
173
+
174
+ def raw
175
+ "%o %s\0%s" % [@mode, @name, [@sha1].pack("H*")]
176
+ end
177
+ end
178
+
179
+
180
+ def self.read_bytes_until(io, char)
181
+ string = ''
182
+ while ((next_char = io.getc.chr) != char) && !io.eof
183
+ string += next_char
184
+ end
185
+ string
186
+ end
187
+
188
+
189
+ class Tree < GitObject
190
+ attr_accessor :entry
191
+
192
+ def self.from_raw(rawobject, repository=nil)
193
+ raw = StringIO.new(rawobject.content)
194
+
195
+ entries = []
196
+ while !raw.eof?
197
+ mode = Grit::GitRuby.read_bytes_until(raw, ' ')
198
+ file_name = Grit::GitRuby.read_bytes_until(raw, "\0")
199
+ raw_sha = raw.read(20)
200
+ sha = raw_sha.unpack("H*").first
201
+
202
+ entries << DirectoryEntry.new(mode, file_name, sha)
203
+ end
204
+ new(entries, repository)
205
+ end
206
+
207
+ def initialize(entries=[], repository = nil)
208
+ @entry = entries
209
+ @repository = repository
210
+ end
211
+
212
+ def type
213
+ :tree
214
+ end
215
+
216
+ def raw_content
217
+ # TODO: sort correctly
218
+ #@entry.sort { |a,b| a.name <=> b.name }.
219
+ @entry.collect { |e| [[e.format_mode, e.format_type, e.sha1].join(' '), e.name].join("\t") }.join("\n")
220
+ end
221
+
222
+ def actual_raw
223
+ #@entry.collect { |e| e.raw.join(' '), e.name].join("\t") }.join("\n")
224
+ end
225
+ end
226
+
227
+ class Commit < GitObject
228
+ attr_accessor :author, :committer, :tree, :parent, :message, :headers
229
+
230
+ def self.from_raw(rawobject, repository=nil)
231
+ parent = []
232
+ tree = author = committer = nil
233
+
234
+ headers, message = rawobject.content.split(/\n\n/, 2)
235
+ all_headers = headers.split(/\n/).map { |header| header.split(/ /, 2) }
236
+ all_headers.each do |key, value|
237
+ case key
238
+ when "tree"
239
+ tree = value
240
+ when "parent"
241
+ parent.push(value)
242
+ when "author"
243
+ author = UserInfo.new(value)
244
+ when "committer"
245
+ committer = UserInfo.new(value)
246
+ else
247
+ warn "unknown header '%s' in commit %s" % \
248
+ [key, rawobject.sha1.unpack("H*")[0]]
249
+ end
250
+ end
251
+ if not tree && author && committer
252
+ raise RuntimeError, "incomplete raw commit object"
253
+ end
254
+ new(tree, parent, author, committer, message, headers, repository)
255
+ end
256
+
257
+ def initialize(tree, parent, author, committer, message, headers, repository=nil)
258
+ @tree = tree
259
+ @author = author
260
+ @parent = parent
261
+ @committer = committer
262
+ @message = message
263
+ @headers = headers
264
+ @repository = repository
265
+ end
266
+
267
+ def type
268
+ :commit
269
+ end
270
+
271
+ def raw_content
272
+ "tree %s\n%sauthor %s\ncommitter %s\n\n" % [
273
+ @tree,
274
+ @parent.collect { |i| "parent %s\n" % i }.join,
275
+ @author, @committer] + @message
276
+ end
277
+
278
+ def raw_log(sha)
279
+ output = "commit #{sha}\n"
280
+ output += @headers + "\n\n"
281
+ output += @message.split("\n").map { |l| ' ' + l }.join("\n") + "\n\n"
282
+ end
283
+
284
+ end
285
+
286
+ class Tag < GitObject
287
+ attr_accessor :object, :type, :tag, :tagger, :message
288
+
289
+ def self.from_raw(rawobject, repository=nil)
290
+
291
+ headers, message = rawobject.content.split(/\n\n/, 2)
292
+ headers = headers.split(/\n/).map { |header| header.split(' ', 2) }
293
+
294
+ object = ''
295
+ type = ''
296
+ tag = ''
297
+ tagger = ''
298
+
299
+ headers.each do |key, value|
300
+ case key
301
+ when "object"
302
+ object = value
303
+ when "type"
304
+ if !["blob", "tree", "commit", "tag"].include?(value)
305
+ raise RuntimeError, "invalid type in tag"
306
+ end
307
+ type = value.to_sym
308
+ when "tag"
309
+ tag = value
310
+ when "tagger"
311
+ tagger = UserInfo.new(value)
312
+ else
313
+ warn "unknown header '%s' in tag" % \
314
+ [key, rawobject.sha1.unpack("H*")[0]]
315
+ end
316
+ end
317
+
318
+ if not object && type && tag && tagger
319
+ raise RuntimeError, "incomplete raw tag object"
320
+ end
321
+ new(object, type, tag, tagger, message, repository)
322
+ end
323
+
324
+ def initialize(object, type, tag, tagger, message, repository=nil)
325
+ @object = object
326
+ @type = type
327
+ @tag = tag
328
+ @tagger = tagger
329
+ @repository = repository
330
+ @message = message
331
+ end
332
+
333
+ def raw_content
334
+ "object %s\ntype %s\ntag %s\ntagger %s\n\n" % \
335
+ [@object, @type, @tag, @tagger] + @message
336
+ end
337
+
338
+ def type
339
+ :tag
340
+ end
341
+ end
342
+
343
+ end
344
+ end