pjhyett-grit 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/History.txt +13 -0
  2. data/Manifest.txt +71 -0
  3. data/README.txt +213 -0
  4. data/Rakefile +29 -0
  5. data/grit.gemspec +62 -0
  6. data/lib/grit.rb +54 -0
  7. data/lib/grit/actor.rb +36 -0
  8. data/lib/grit/blob.rb +117 -0
  9. data/lib/grit/commit.rb +229 -0
  10. data/lib/grit/commit_stats.rb +104 -0
  11. data/lib/grit/config.rb +44 -0
  12. data/lib/grit/diff.rb +70 -0
  13. data/lib/grit/errors.rb +7 -0
  14. data/lib/grit/git-ruby.rb +184 -0
  15. data/lib/grit/git-ruby/commit_db.rb +52 -0
  16. data/lib/grit/git-ruby/file_index.rb +186 -0
  17. data/lib/grit/git-ruby/git_object.rb +344 -0
  18. data/lib/grit/git-ruby/internal/loose.rb +137 -0
  19. data/lib/grit/git-ruby/internal/mmap.rb +58 -0
  20. data/lib/grit/git-ruby/internal/pack.rb +382 -0
  21. data/lib/grit/git-ruby/internal/raw_object.rb +37 -0
  22. data/lib/grit/git-ruby/object.rb +319 -0
  23. data/lib/grit/git-ruby/repository.rb +731 -0
  24. data/lib/grit/git.rb +122 -0
  25. data/lib/grit/head.rb +83 -0
  26. data/lib/grit/index.rb +121 -0
  27. data/lib/grit/lazy.rb +33 -0
  28. data/lib/grit/ref.rb +95 -0
  29. data/lib/grit/repo.rb +387 -0
  30. data/lib/grit/status.rb +151 -0
  31. data/lib/grit/tag.rb +71 -0
  32. data/lib/grit/tree.rb +104 -0
  33. data/test/test_actor.rb +35 -0
  34. data/test/test_blob.rb +79 -0
  35. data/test/test_commit.rb +184 -0
  36. data/test/test_config.rb +58 -0
  37. data/test/test_diff.rb +18 -0
  38. data/test/test_git.rb +70 -0
  39. data/test/test_grit.rb +32 -0
  40. data/test/test_head.rb +47 -0
  41. data/test/test_real.rb +19 -0
  42. data/test/test_reality.rb +17 -0
  43. data/test/test_remote.rb +14 -0
  44. data/test/test_repo.rb +277 -0
  45. data/test/test_tag.rb +25 -0
  46. data/test/test_tree.rb +96 -0
  47. metadata +128 -0
data/lib/grit/git.rb ADDED
@@ -0,0 +1,122 @@
1
+ module Grit
2
+
3
+ class Git
4
+ class GitTimeout < RuntimeError
5
+ attr_reader :command, :bytes_read
6
+
7
+ def initialize(command = nil, bytes_read = nil)
8
+ @command = command
9
+ @bytes_read = bytes_read
10
+ end
11
+ end
12
+
13
+ undef_method :clone
14
+
15
+ include GitRuby
16
+
17
+ class << self
18
+ attr_accessor :git_binary, :git_timeout
19
+ end
20
+
21
+ self.git_binary = "/usr/bin/env git"
22
+ self.git_timeout = 5
23
+
24
+ attr_accessor :git_dir, :bytes_read
25
+
26
+ def initialize(git_dir)
27
+ self.git_dir = git_dir
28
+ self.bytes_read = 0
29
+ end
30
+
31
+ # Run the given git command with the specified arguments and return
32
+ # the result as a String
33
+ # +cmd+ is the command
34
+ # +options+ is a hash of Ruby style options
35
+ # +args+ is the list of arguments (to be joined by spaces)
36
+ #
37
+ # Examples
38
+ # git.rev_list({:max_count => 10, :header => true}, "master")
39
+ #
40
+ # Returns String
41
+ def method_missing(cmd, options = {}, *args)
42
+ run('', cmd, '', options, args)
43
+ end
44
+
45
+ def run(prefix, cmd, postfix, options, args)
46
+ timeout = options.delete(:timeout)
47
+ timeout = true if timeout.nil?
48
+
49
+ opt_args = transform_options(options)
50
+ ext_args = args.reject { |a| a.empty? }.map { |a| (a == '--' || a[0].chr == '|') ? a : "'#{a}'" }
51
+
52
+ call = "#{prefix}#{Git.git_binary} --git-dir='#{self.git_dir}' #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{postfix}"
53
+ Grit.log(call) if Grit.debug
54
+ response, err = timeout ? sh(call) : wild_sh(call)
55
+ Grit.log(response) if Grit.debug
56
+ Grit.log(err) if Grit.debug
57
+ response
58
+ end
59
+
60
+ def sh(command)
61
+ ret, pid, err = nil, nil, nil
62
+ Open4.popen4(command) do |id, _, stdout, stderr|
63
+ pid = id
64
+ ret = Timeout.timeout(self.class.git_timeout) { stdout.read }
65
+ err = stderr.read
66
+ @bytes_read += ret.size
67
+
68
+ if @bytes_read > 5242880 # 5.megabytes
69
+ bytes = @bytes_read
70
+ @bytes_read = 0
71
+ raise GitTimeout.new(command, bytes)
72
+ end
73
+ end
74
+ [ret, err]
75
+ rescue Errno::ECHILD
76
+ [ret, err]
77
+ rescue Timeout::Error, Grit::Git::GitTimeout
78
+ bytes = @bytes_read
79
+ @bytes_read = 0
80
+ raise GitTimeout.new(command, bytes)
81
+ end
82
+
83
+ def wild_sh(command)
84
+ ret, err = nil, nil
85
+ Open4.popen4(command) do |pid, _, stdout, stderr|
86
+ ret = stdout.read
87
+ err = stderr.read
88
+ end
89
+ [ret, err]
90
+ rescue Errno::ECHILD
91
+ [ret, err]
92
+ end
93
+
94
+ # Transform Ruby style options into git command line options
95
+ # +options+ is a hash of Ruby style options
96
+ #
97
+ # Returns String[]
98
+ # e.g. ["--max-count=10", "--header"]
99
+ def transform_options(options)
100
+ args = []
101
+ options.keys.each do |opt|
102
+ if opt.to_s.size == 1
103
+ if options[opt] == true
104
+ args << "-#{opt}"
105
+ else
106
+ val = options.delete(opt)
107
+ args << "-#{opt.to_s} '#{val}'"
108
+ end
109
+ else
110
+ if options[opt] == true
111
+ args << "--#{opt.to_s.gsub(/_/, '-')}"
112
+ else
113
+ val = options.delete(opt)
114
+ args << "--#{opt.to_s.gsub(/_/, '-')}='#{val}'"
115
+ end
116
+ end
117
+ end
118
+ args
119
+ end
120
+ end # Git
121
+
122
+ end # Grit
data/lib/grit/head.rb ADDED
@@ -0,0 +1,83 @@
1
+ module Grit
2
+ HEAD_PREFIX = 'refs/heads'
3
+
4
+ # A Head is a named reference to a Commit. Every Head instance contains a name
5
+ # and a Commit object.
6
+ #
7
+ # r = Grit::Repo.new("/path/to/repo")
8
+ # h = r.heads.first
9
+ # h.name # => "master"
10
+ # h.commit # => #<Grit::Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
11
+ # h.commit.id # => "1c09f116cbc2cb4100fb6935bb162daa4723f455"
12
+ class Head
13
+ attr_reader :name
14
+ attr_reader :commit
15
+
16
+ # Instantiate a new Head
17
+ # +name+ is the name of the head
18
+ # +commit+ is the Commit that the head points to
19
+ #
20
+ # Returns Grit::Head (baked)
21
+ def initialize(name, commit)
22
+ @name = name
23
+ @commit = commit
24
+ end
25
+
26
+ # Find all Heads
27
+ # +repo+ is the Repo
28
+ # +options+ is a Hash of options
29
+ #
30
+ # Returns Grit::Head[] (baked)
31
+ def self.find_all(repo, options = {})
32
+ default_options = {
33
+ :sort => "committerdate",
34
+ :format => "%(refname)%00%(objectname)",
35
+ :timeout => false
36
+ }
37
+
38
+ actual_options = default_options.merge(options)
39
+
40
+ output = repo.git.for_each_ref(actual_options, HEAD_PREFIX)
41
+
42
+ self.list_from_string(repo, output)
43
+ end
44
+
45
+ # Parse out head information into an array of baked head objects
46
+ # +repo+ is the Repo
47
+ # +text+ is the text output from the git command
48
+ #
49
+ # Returns Grit::Head[] (baked)
50
+ def self.list_from_string(repo, text)
51
+ heads = []
52
+
53
+ text.split("\n").each do |line|
54
+ heads << self.from_string(repo, line)
55
+ end
56
+
57
+ heads
58
+ end
59
+
60
+ # Create a new Head instance from the given string.
61
+ # +repo+ is the Repo
62
+ # +line+ is the formatted head information
63
+ #
64
+ # Format
65
+ # name: [a-zA-Z_/]+
66
+ # <null byte>
67
+ # id: [0-9A-Fa-f]{40}
68
+ #
69
+ # Returns Grit::Head (baked)
70
+ def self.from_string(repo, line)
71
+ full_name, id = line.split("\0")
72
+ name = full_name.sub("#{HEAD_PREFIX}/", '')
73
+ commit = Commit.create(repo, :id => id)
74
+ self.new(name, commit)
75
+ end
76
+
77
+ # Pretty object inspection
78
+ def inspect
79
+ %Q{#<Grit::Head "#{@name}">}
80
+ end
81
+ end # Head
82
+
83
+ end # Grit
data/lib/grit/index.rb ADDED
@@ -0,0 +1,121 @@
1
+ module Grit
2
+
3
+ class Index
4
+ attr_accessor :repo, :tree, :current_tree
5
+
6
+ def initialize(repo)
7
+ self.repo = repo
8
+ self.tree = {}
9
+ self.current_tree = nil
10
+ end
11
+
12
+ # Add a file to the index
13
+ # +path+ is the path (including filename)
14
+ # +data+ is the binary contents of the file
15
+ #
16
+ # Returns nothing
17
+ def add(file_path, data)
18
+ path = file_path.split('/')
19
+ filename = path.pop
20
+
21
+ current = self.tree
22
+
23
+ path.each do |dir|
24
+ current[dir] ||= {}
25
+ node = current[dir]
26
+ current = node
27
+ end
28
+
29
+ current[filename] = data
30
+ end
31
+
32
+ def read_tree(tree)
33
+ self.current_tree = self.repo.tree(tree)
34
+ end
35
+
36
+ # Commit the contents of the index
37
+ # +message+ is the commit message
38
+ #
39
+ # Returns a String of the SHA1 of the commit
40
+ def commit(message, parents = nil, actor = nil, last_tree = nil, head = 'master')
41
+ tree_sha1 = write_tree(self.tree, self.current_tree)
42
+ return false if tree_sha1 == last_tree # don't write identical commits
43
+
44
+ contents = []
45
+ contents << ['tree', tree_sha1].join(' ')
46
+ parents.each do |p|
47
+ contents << ['parent', p].join(' ') if p
48
+ end if parents
49
+
50
+ if actor
51
+ name = actor.name
52
+ email = actor.email
53
+ else
54
+ config = Config.new(self.repo)
55
+ name = config['user.name']
56
+ email = config['user.email']
57
+ end
58
+
59
+ author_string = "#{name} <#{email}> #{Time.now.to_i} -0700" # !! TODO : gotta fix this
60
+ contents << ['author', author_string].join(' ')
61
+ contents << ['committer', author_string].join(' ')
62
+ contents << ''
63
+ contents << message
64
+
65
+ commit_sha1 = self.repo.git.ruby_git.put_raw_object(contents.join("\n"), 'commit')
66
+
67
+ # self.repo.git.update_ref({}, 'HEAD', commit_sha1)
68
+ ref_heads = File.join(self.repo.path, 'refs', 'heads')
69
+ FileUtils.mkdir_p(ref_heads)
70
+ File.open(File.join(ref_heads, head), 'w') do |f|
71
+ f.write(commit_sha1)
72
+ end if commit_sha1
73
+
74
+ commit_sha1
75
+ end
76
+
77
+ # Recursively write a tree to the index
78
+ # +tree+ is the tree
79
+ #
80
+ # Returns the SHA1 String of the tree
81
+ def write_tree(tree, now_tree = nil)
82
+ tree_contents = {}
83
+
84
+ # fill in original tree
85
+ now_tree.contents.each do |obj|
86
+ sha = [obj.id].pack("H*")
87
+ k = obj.name
88
+ k += '/' if (obj.class == Grit::Tree)
89
+ tree_contents[k] = "%s %s\0%s" % [obj.mode.to_s, obj.name, sha]
90
+ end if now_tree
91
+
92
+ # overwrite with new tree contents
93
+ tree.each do |k, v|
94
+ case v
95
+ when String:
96
+ sha = write_blob(v)
97
+ sha = [sha].pack("H*")
98
+ str = "%s %s\0%s" % ['100644', k, sha]
99
+ tree_contents[k] = str
100
+ when Hash:
101
+ ctree = now_tree/k if now_tree
102
+ sha = write_tree(v, ctree)
103
+ sha = [sha].pack("H*")
104
+ str = "%s %s\0%s" % ['040000', k, sha]
105
+ tree_contents[k + '/'] = str
106
+ end
107
+ end
108
+ tr = tree_contents.sort.map { |k, v| v }.join('')
109
+ self.repo.git.ruby_git.put_raw_object(tr, 'tree')
110
+ end
111
+
112
+ # Write the blob to the index
113
+ # +data+ is the data to write
114
+ #
115
+ # Returns the SHA1 String of the blob
116
+ def write_blob(data)
117
+ self.repo.git.ruby_git.put_raw_object(data, 'blob')
118
+ end
119
+ end # Index
120
+
121
+ end # Grit
data/lib/grit/lazy.rb ADDED
@@ -0,0 +1,33 @@
1
+ ##
2
+ # Allows attributes to be declared as lazy, meaning that they won't be
3
+ # computed until they are asked for.
4
+ #
5
+ # Works by delegating each lazy_reader to a cached lazy_source method.
6
+ #
7
+ # class Person
8
+ # lazy_reader :eyes
9
+ #
10
+ # def lazy_source
11
+ # OpenStruct.new(:eyes => 2)
12
+ # end
13
+ # end
14
+ #
15
+ # >> Person.new.eyes
16
+ # => 2
17
+ #
18
+ module Lazy
19
+ def lazy_reader(*args)
20
+ args.each do |arg|
21
+ ivar = "@#{arg}"
22
+ define_method(arg) do
23
+ if instance_variable_defined?(ivar)
24
+ val = instance_variable_get(ivar)
25
+ return val if val
26
+ end
27
+ instance_variable_set(ivar, (@lazy_source ||= lazy_source).send(arg))
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ Object.extend Lazy unless Object.ancestors.include? Lazy
data/lib/grit/ref.rb ADDED
@@ -0,0 +1,95 @@
1
+ module Grit
2
+
3
+ class Ref
4
+
5
+ class << self
6
+
7
+ # Find all Refs
8
+ # +repo+ is the Repo
9
+ # +options+ is a Hash of options
10
+ #
11
+ # Returns Grit::Ref[] (baked)
12
+ def find_all(repo, options = {})
13
+ refs = []
14
+
15
+ Dir.chdir(repo.path) do
16
+ if File.file?('packed-refs')
17
+ File.readlines('packed-refs').each do |line|
18
+ if m = /^(\w{40}) (.*?)$/.match(line)
19
+ next if !Regexp.new('^' + prefix).match(m[2])
20
+ name = m[2].sub("#{prefix}/", '')
21
+ commit = Commit.create(repo, :id => m[1])
22
+ refs << self.new(name, commit)
23
+ end
24
+ end
25
+ end
26
+
27
+ files = Dir.glob(prefix + '/**/*')
28
+ files.each do |ref|
29
+ next if !File.file?(ref)
30
+ id = File.read(ref).chomp
31
+ name = ref.sub("#{prefix}/", '')
32
+ commit = Commit.create(repo, :id => id)
33
+ refs << self.new(name, commit)
34
+ end
35
+ end
36
+
37
+ refs
38
+ end
39
+
40
+ protected
41
+
42
+ def prefix
43
+ "refs/#{name.to_s.gsub(/^.*::/, '').downcase}s"
44
+ end
45
+
46
+ end
47
+
48
+ attr_reader :name
49
+ attr_reader :commit
50
+
51
+ # Instantiate a new Head
52
+ # +name+ is the name of the head
53
+ # +commit+ is the Commit that the head points to
54
+ #
55
+ # Returns Grit::Head (baked)
56
+ def initialize(name, commit)
57
+ @name = name
58
+ @commit = commit
59
+ end
60
+
61
+ # Pretty object inspection
62
+ def inspect
63
+ %Q{#<#{self.class.name} "#{@name}">}
64
+ end
65
+ end # Ref
66
+
67
+ # A Head is a named reference to a Commit. Every Head instance contains a name
68
+ # and a Commit object.
69
+ #
70
+ # r = Grit::Repo.new("/path/to/repo")
71
+ # h = r.heads.first
72
+ # h.name # => "master"
73
+ # h.commit # => #<Grit::Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
74
+ # h.commit.id # => "1c09f116cbc2cb4100fb6935bb162daa4723f455"
75
+ class Head < Ref
76
+
77
+ # Get the HEAD revision of the repo.
78
+ # +repo+ is the Repo
79
+ # +options+ is a Hash of options
80
+ #
81
+ # Returns Grit::Head (baked)
82
+ def self.current(repo, options = {})
83
+ head = File.open(File.join(repo.path, 'HEAD')).read.chomp
84
+ if /ref: refs\/heads\/(.*)/.match(head)
85
+ self.new($1, repo.git.rev_parse(options, 'HEAD'))
86
+ end
87
+ end
88
+
89
+ end # Head
90
+
91
+ class Tag < Ref ; end
92
+
93
+ class Remote < Ref; end
94
+
95
+ end # Grit