steveh-grit 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,186 @@
1
+ require 'grit/git-ruby/repository'
2
+ require 'grit/git-ruby/file_index'
3
+
4
+ module Grit
5
+
6
+ # the functions in this module intercept the calls to git binary
7
+ # made buy the grit objects and attempts to run them in pure ruby
8
+ # if it will be faster, or if the git binary is not available (!!TODO!!)
9
+ module GitRuby
10
+
11
+ attr_accessor :ruby_git_repo, :git_file_index
12
+
13
+ def init(options)
14
+ if options.size == 0
15
+ Grit::GitRuby::Repository.init(@git_dir)
16
+ else
17
+ method_missing('init', options)
18
+ end
19
+ end
20
+
21
+ def cat_file(options, ref)
22
+ if options[:t]
23
+ file_type(ref)
24
+ elsif options[:s]
25
+ file_size(ref)
26
+ elsif options[:p]
27
+ try_run { ruby_git.cat_file(ref) }
28
+ end
29
+ rescue Grit::GitRuby::Repository::NoSuchShaFound
30
+ ''
31
+ end
32
+
33
+ # lib/grit/tree.rb:16: output = repo.git.ls_tree({}, treeish, *paths)
34
+ def ls_tree(options, treeish, *paths)
35
+ sha = rev_parse({}, treeish)
36
+ ruby_git.ls_tree(sha, paths.flatten)
37
+ rescue Grit::GitRuby::Repository::NoSuchShaFound
38
+ ''
39
+ end
40
+
41
+ # git diff --full-index 'ec037431382e83c3e95d4f2b3d145afbac8ea55d' 'f1ec1aea10986159456846b8a05615b87828d6c6'
42
+ def diff(options, sha1, sha2)
43
+ try_run { ruby_git.diff(sha1, sha2, options) }
44
+ end
45
+
46
+ def rev_list(options, ref = 'master')
47
+ options.delete(:skip) if options[:skip].to_i == 0
48
+ allowed_options = [:max_count, :since, :until, :pretty] # this is all I can do right now
49
+ if ((options.keys - allowed_options).size > 0)
50
+ return method_missing('rev-list', options, ref)
51
+ elsif (options.size == 0)
52
+ # pure rev-list
53
+ begin
54
+ return file_index.commits_from(rev_parse({}, ref)).join("\n") + "\n"
55
+ rescue
56
+ return method_missing('rev-list', options, ref)
57
+ end
58
+ else
59
+ aref = rev_parse({}, ref)
60
+ if aref.is_a? Array
61
+ return method_missing('rev-list', options, ref)
62
+ else
63
+ return try_run { ruby_git.rev_list(aref, options) }
64
+ end
65
+ end
66
+ end
67
+
68
+ def rev_parse(options, string)
69
+ raise RuntimeError, "invalid string: #{string}" unless string.is_a?(String)
70
+
71
+ if string =~ /\.\./
72
+ (sha1, sha2) = string.split('..')
73
+ return [rev_parse({}, sha1), rev_parse({}, sha2)]
74
+ end
75
+
76
+ if /^[0-9a-f]{40}$/.match(string) # passing in a sha - just no-op it
77
+ return string.chomp
78
+ end
79
+
80
+ head = File.join(@git_dir, 'refs', 'heads', string)
81
+ return File.read(head).chomp if File.file?(head)
82
+
83
+ head = File.join(@git_dir, 'refs', 'remotes', string)
84
+ return File.read(head).chomp if File.file?(head)
85
+
86
+ head = File.join(@git_dir, 'refs', 'tags', string)
87
+ return File.read(head).chomp if File.file?(head)
88
+
89
+ ## check packed-refs file, too
90
+ packref = File.join(@git_dir, 'packed-refs')
91
+ if File.file?(packref)
92
+ File.readlines(packref).each do |line|
93
+ if m = /^(\w{40}) refs\/.+?\/(.*?)$/.match(line)
94
+ next if !Regexp.new(Regexp.escape(string) + '$').match(m[3])
95
+ return m[1].chomp
96
+ end
97
+ end
98
+ end
99
+
100
+ ## !! more - partials and such !!
101
+
102
+ # revert to calling git - grr
103
+ return method_missing('rev-parse', {}, string).chomp
104
+ end
105
+
106
+ def file_size(ref)
107
+ try_run { ruby_git.cat_file_size(ref).to_s }
108
+ end
109
+
110
+ def file_type(ref)
111
+ try_run { ruby_git.cat_file_type(ref).to_s }
112
+ end
113
+
114
+ def blame_tree(commit, path = nil)
115
+ begin
116
+ path = [path].join('/').to_s + '/' if (path && path != '')
117
+ path = '' if !path.is_a? String
118
+ commits = file_index.last_commits(rev_parse({}, commit), looking_for(commit, path))
119
+ clean_paths(commits)
120
+ rescue FileIndex::IndexFileNotFound
121
+ {}
122
+ end
123
+ end
124
+
125
+ def file_index
126
+ @git_file_index ||= FileIndex.new(@git_dir)
127
+ end
128
+
129
+ def ruby_git
130
+ @ruby_git_repo ||= Repository.new(@git_dir)
131
+ end
132
+
133
+ private
134
+
135
+ def try_run
136
+ ret = ''
137
+ Timeout.timeout(self.class.git_timeout) do
138
+ ret = yield
139
+ end
140
+ @bytes_read += ret.size
141
+
142
+ #if @bytes_read > 5242880 # 5.megabytes
143
+ # bytes = @bytes_read
144
+ # @bytes_read = 0
145
+ # raise Grit::Git::GitTimeout.new(command, bytes)
146
+ #end
147
+
148
+ ret
149
+ rescue Timeout::Error => e
150
+ bytes = @bytes_read
151
+ @bytes_read = 0
152
+ raise Grit::Git::GitTimeout.new(command, bytes)
153
+ end
154
+
155
+ def looking_for(commit, path = nil)
156
+ tree_sha = ruby_git.get_subtree(rev_parse({}, commit), path)
157
+
158
+ looking_for = []
159
+ ruby_git.get_object_by_sha1(tree_sha).entry.each do |e|
160
+ if path && !(path == '' || path == '.' || path == './')
161
+ file = File.join(path, e.name)
162
+ else
163
+ file = e.name
164
+ end
165
+ file += '/' if e.type == :directory
166
+ looking_for << file
167
+ end
168
+ looking_for
169
+ end
170
+
171
+ def clean_paths(commit_array)
172
+ new_commits = {}
173
+ commit_array.each do |file, sha|
174
+ file = file.chop if file[file.size - 1 , 1] == '/'
175
+ new_commits[file] = sha
176
+ end
177
+ new_commits
178
+ end
179
+
180
+ # TODO
181
+ # git grep -n 'foo' 'master'
182
+ # git log --pretty='raw' --max-count='1' 'master' -- 'LICENSE'
183
+ # git log --pretty='raw' --max-count='1' 'master' -- 'test'
184
+
185
+ end
186
+ end
data/lib/grit/git.rb ADDED
@@ -0,0 +1,146 @@
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, :git_max_size
19
+ end
20
+
21
+ self.git_binary = "/usr/bin/env git"
22
+ self.git_timeout = 10
23
+ self.git_max_size = 5242880 # 5.megabytes
24
+
25
+ def self.with_timeout(timeout = 10.seconds)
26
+ old_timeout = Grit::Git.git_timeout
27
+ Grit::Git.git_timeout = timeout
28
+ yield
29
+ Grit::Git.git_timeout = old_timeout
30
+ end
31
+
32
+ attr_accessor :git_dir, :work_tree, :bytes_read
33
+
34
+ def initialize(git_dir, work_tree=nil)
35
+ self.git_dir = git_dir
36
+ self.work_tree = work_tree
37
+ self.bytes_read = 0
38
+ end
39
+
40
+ def shell_escape(str)
41
+ str.to_s.gsub("'", "\\\\'").gsub(";", '\\;')
42
+ end
43
+ alias_method :e, :shell_escape
44
+
45
+ # Run the given git command with the specified arguments and return
46
+ # the result as a String
47
+ # +cmd+ is the command
48
+ # +options+ is a hash of Ruby style options
49
+ # +args+ is the list of arguments (to be joined by spaces)
50
+ #
51
+ # Examples
52
+ # git.rev_list({:max_count => 10, :header => true}, "master")
53
+ #
54
+ # Returns String
55
+ def method_missing(cmd, options = {}, *args)
56
+ run('', cmd, '', options, args)
57
+ end
58
+
59
+ def git_options
60
+ { :git_dir => self.git_dir, :work_tree => self.work_tree }
61
+ end
62
+
63
+ def run(prefix, cmd, postfix, options, args)
64
+ timeout = options.delete(:timeout) rescue nil
65
+ timeout = true if timeout.nil?
66
+
67
+ git_opt_args = transform_options(git_options)
68
+ opt_args = transform_options(options)
69
+ ext_args = args.reject { |a| a.empty? }.map { |a| (a == '--' || a[0].chr == '|') ? a : "'#{e(a)}'" }
70
+
71
+ call = "#{prefix}#{Git.git_binary} #{git_opt_args} #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{e(postfix)}"
72
+ Grit.log(call) if Grit.debug
73
+ response, err = timeout ? sh(call) : wild_sh(call)
74
+ Grit.log(response) if Grit.debug
75
+ Grit.log(err) if Grit.debug
76
+ response
77
+ end
78
+
79
+ def sh(command)
80
+ ret, err = '', ''
81
+ Open3.popen3(command) do |_, stdout, stderr|
82
+ Timeout.timeout(self.class.git_timeout) do
83
+ while tmp = stdout.read(1024)
84
+ ret += tmp
85
+ if (@bytes_read += tmp.size) > self.class.git_max_size
86
+ bytes = @bytes_read
87
+ @bytes_read = 0
88
+ raise GitTimeout.new(command, bytes)
89
+ end
90
+ end
91
+ end
92
+
93
+ while tmp = stderr.read(1024)
94
+ err += tmp
95
+ end
96
+ end
97
+ [ret, err]
98
+ rescue Timeout::Error, Grit::Git::GitTimeout
99
+ bytes = @bytes_read
100
+ @bytes_read = 0
101
+ raise GitTimeout.new(command, bytes)
102
+ end
103
+
104
+ def wild_sh(command)
105
+ ret, err = '', ''
106
+ Open3.popen3(command) do |_, stdout, stderr|
107
+ while tmp = stdout.read(1024)
108
+ ret += tmp
109
+ end
110
+
111
+ while tmp = stderr.read(1024)
112
+ err += tmp
113
+ end
114
+ end
115
+ [ret, err]
116
+ end
117
+
118
+ # Transform Ruby style options into git command line options
119
+ # +options+ is a hash of Ruby style options
120
+ #
121
+ # Returns String[]
122
+ # e.g. ["--max-count=10", "--header"]
123
+ def transform_options(options)
124
+ args = []
125
+ options.keys.each do |opt|
126
+ if opt.to_s.size == 1
127
+ if options[opt] == true
128
+ args << "-#{opt}"
129
+ else
130
+ val = options.delete(opt)
131
+ args << "-#{opt.to_s} '#{e(val)}'"
132
+ end
133
+ else
134
+ if options[opt] == true
135
+ args << "--#{opt.to_s.gsub(/_/, '-')}"
136
+ else
137
+ val = options.delete(opt)
138
+ args << "--#{opt.to_s.gsub(/_/, '-')}='#{e(val)}'"
139
+ end
140
+ end
141
+ end
142
+ args
143
+ end
144
+ end # Git
145
+
146
+ end # Grit
data/lib/grit/index.rb ADDED
@@ -0,0 +1,122 @@
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
+ # Sets the current tree
33
+ # +tree+ the branch/tag/sha... to use - a string
34
+ #
35
+ # Returns index (self)
36
+ def read_tree(tree)
37
+ self.current_tree = self.repo.tree(tree)
38
+ end
39
+
40
+ # Commit the contents of the index
41
+ # +message+ is the commit message [nil]
42
+ # +parents+ is one or more commits to attach this commit to to form a new head [nil]
43
+ # +actor+ is the details of the user making the commit [nil]
44
+ # +last_tree+ is a tree to compare with - to avoid making empty commits [nil]
45
+ # +head+ is the branch to write this head to [master]
46
+ #
47
+ # Returns a String of the SHA1 of the commit
48
+ def commit(message, parents = nil, actor = nil, last_tree = nil, head = 'master')
49
+ tree_sha1 = write_tree(self.tree, self.current_tree)
50
+ return false if tree_sha1 == last_tree # don't write identical commits
51
+
52
+ contents = []
53
+ contents << ['tree', tree_sha1].join(' ')
54
+ parents.each do |p|
55
+ contents << ['parent', p].join(' ') if p
56
+ end if parents
57
+
58
+ if actor
59
+ name = actor.name
60
+ email = actor.email
61
+ else
62
+ config = Config.new(self.repo)
63
+ name = config['user.name']
64
+ email = config['user.email']
65
+ end
66
+
67
+ author_string = "#{name} <#{email}> #{Time.now.to_i} -0700" # !! TODO : gotta fix this
68
+ contents << ['author', author_string].join(' ')
69
+ contents << ['committer', author_string].join(' ')
70
+ contents << ''
71
+ contents << message
72
+
73
+ commit_sha1 = self.repo.git.ruby_git.put_raw_object(contents.join("\n"), 'commit')
74
+
75
+ self.repo.update_ref(head, commit_sha1)
76
+ end
77
+
78
+ # Recursively write a tree to the index
79
+ # +tree+ is the tree
80
+ #
81
+ # Returns the SHA1 String of the tree
82
+ def write_tree(tree, now_tree = nil)
83
+ tree_contents = {}
84
+
85
+ # fill in original tree
86
+ now_tree.contents.each do |obj|
87
+ sha = [obj.id].pack("H*")
88
+ k = obj.name
89
+ k += '/' if (obj.class == Grit::Tree)
90
+ tree_contents[k] = "%s %s\0%s" % [obj.mode.to_s, obj.name, sha]
91
+ end if now_tree
92
+
93
+ # overwrite with new tree contents
94
+ tree.each do |k, v|
95
+ case v
96
+ when String
97
+ sha = write_blob(v)
98
+ sha = [sha].pack("H*")
99
+ str = "%s %s\0%s" % ['100644', k, sha]
100
+ tree_contents[k] = str
101
+ when Hash
102
+ ctree = now_tree/k if now_tree
103
+ sha = write_tree(v, ctree)
104
+ sha = [sha].pack("H*")
105
+ str = "%s %s\0%s" % ['040000', k, sha]
106
+ tree_contents[k + '/'] = str
107
+ end
108
+ end
109
+ tr = tree_contents.sort.map { |k, v| v }.join('')
110
+ self.repo.git.ruby_git.put_raw_object(tr, 'tree')
111
+ end
112
+
113
+ # Write the blob to the index
114
+ # +data+ is the data to write
115
+ #
116
+ # Returns the SHA1 String of the blob
117
+ def write_blob(data)
118
+ self.repo.git.ruby_git.put_raw_object(data, 'blob')
119
+ end
120
+ end # Index
121
+
122
+ 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/merge.rb ADDED
@@ -0,0 +1,45 @@
1
+ module Grit
2
+
3
+ class Merge
4
+
5
+ STATUS_BOTH = 'both'
6
+ STATUS_OURS = 'ours'
7
+ STATUS_THEIRS = 'theirs'
8
+
9
+ attr_reader :conflicts, :text, :sections
10
+
11
+ def initialize(str)
12
+ status = STATUS_BOTH
13
+
14
+ section = 1
15
+ @conflicts = 0
16
+ @text = {}
17
+
18
+ lines = str.split("\n")
19
+ lines.each do |line|
20
+ if /^<<<<<<< (.*?)/.match(line)
21
+ status = STATUS_OURS
22
+ @conflicts += 1
23
+ section += 1
24
+ elsif line == '======='
25
+ status = STATUS_THEIRS
26
+ elsif /^>>>>>>> (.*?)/.match(line)
27
+ status = STATUS_BOTH
28
+ section += 1
29
+ else
30
+ @text[section] ||= {}
31
+ @text[section][status] ||= []
32
+ @text[section][status] << line
33
+ end
34
+ end
35
+ @text = @text.values
36
+ @sections = @text.size
37
+ end
38
+
39
+ # Pretty object inspection
40
+ def inspect
41
+ %Q{#<Grit::Merge}
42
+ end
43
+ end # Merge
44
+
45
+ end # Grit
data/lib/grit/ref.rb ADDED
@@ -0,0 +1,99 @@
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
+ already = {}
15
+ Dir.chdir(repo.path) do
16
+ files = Dir.glob(prefix + '/**/*')
17
+ files.each do |ref|
18
+ next if !File.file?(ref)
19
+ id = File.read(ref).chomp
20
+ name = ref.sub("#{prefix}/", '')
21
+ commit = Commit.create(repo, :id => id)
22
+ if !already[name]
23
+ refs << self.new(name, commit)
24
+ already[name] = true
25
+ end
26
+ end
27
+
28
+ if File.file?('packed-refs')
29
+ File.readlines('packed-refs').each do |line|
30
+ if m = /^(\w{40}) (.*?)$/.match(line)
31
+ next if !Regexp.new('^' + prefix).match(m[2])
32
+ name = m[2].sub("#{prefix}/", '')
33
+ commit = Commit.create(repo, :id => m[1])
34
+ if !already[name]
35
+ refs << self.new(name, commit)
36
+ already[name] = true
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ refs
44
+ end
45
+
46
+ protected
47
+
48
+ def prefix
49
+ "refs/#{name.to_s.gsub(/^.*::/, '').downcase}s"
50
+ end
51
+
52
+ end
53
+
54
+ attr_reader :name
55
+ attr_reader :commit
56
+
57
+ # Instantiate a new Head
58
+ # +name+ is the name of the head
59
+ # +commit+ is the Commit that the head points to
60
+ #
61
+ # Returns Grit::Head (baked)
62
+ def initialize(name, commit)
63
+ @name = name
64
+ @commit = commit
65
+ end
66
+
67
+ # Pretty object inspection
68
+ def inspect
69
+ %Q{#<#{self.class.name} "#{@name}">}
70
+ end
71
+ end # Ref
72
+
73
+ # A Head is a named reference to a Commit. Every Head instance contains a name
74
+ # and a Commit object.
75
+ #
76
+ # r = Grit::Repo.new("/path/to/repo")
77
+ # h = r.heads.first
78
+ # h.name # => "master"
79
+ # h.commit # => #<Grit::Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
80
+ # h.commit.id # => "1c09f116cbc2cb4100fb6935bb162daa4723f455"
81
+ class Head < Ref
82
+
83
+ # Get the HEAD revision of the repo.
84
+ # +repo+ is the Repo
85
+ # +options+ is a Hash of options
86
+ #
87
+ # Returns Grit::Head (baked)
88
+ def self.current(repo, options = {})
89
+ head = File.open(File.join(repo.path, 'HEAD')).read.chomp
90
+ if /ref: refs\/heads\/(.*)/.match(head)
91
+ self.new($1, repo.git.rev_parse(options, 'HEAD'))
92
+ end
93
+ end
94
+
95
+ end # Head
96
+
97
+ class Remote < Ref; end
98
+
99
+ end # Grit