boof-grit 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/API.txt +101 -0
  2. data/History.txt +53 -0
  3. data/LICENSE +22 -0
  4. data/README.md +210 -0
  5. data/VERSION.yml +4 -0
  6. data/examples/ex_add_commit.rb +13 -0
  7. data/examples/ex_index.rb +14 -0
  8. data/lib/grit.rb +68 -0
  9. data/lib/grit/actor.rb +36 -0
  10. data/lib/grit/blame.rb +61 -0
  11. data/lib/grit/blob.rb +126 -0
  12. data/lib/grit/commit.rb +246 -0
  13. data/lib/grit/commit_stats.rb +128 -0
  14. data/lib/grit/config.rb +44 -0
  15. data/lib/grit/diff.rb +70 -0
  16. data/lib/grit/errors.rb +7 -0
  17. data/lib/grit/git-ruby.rb +186 -0
  18. data/lib/grit/git-ruby/commit_db.rb +52 -0
  19. data/lib/grit/git-ruby/file_index.rb +193 -0
  20. data/lib/grit/git-ruby/git_object.rb +350 -0
  21. data/lib/grit/git-ruby/internal/file_window.rb +58 -0
  22. data/lib/grit/git-ruby/internal/loose.rb +137 -0
  23. data/lib/grit/git-ruby/internal/pack.rb +382 -0
  24. data/lib/grit/git-ruby/internal/raw_object.rb +37 -0
  25. data/lib/grit/git-ruby/object.rb +325 -0
  26. data/lib/grit/git-ruby/repository.rb +740 -0
  27. data/lib/grit/git.rb +141 -0
  28. data/lib/grit/index.rb +122 -0
  29. data/lib/grit/lazy.rb +33 -0
  30. data/lib/grit/merge.rb +45 -0
  31. data/lib/grit/ref.rb +177 -0
  32. data/lib/grit/repo.rb +452 -0
  33. data/lib/grit/ruby1.9.rb +7 -0
  34. data/lib/grit/status.rb +151 -0
  35. data/lib/grit/submodule.rb +88 -0
  36. data/lib/grit/tag.rb +66 -0
  37. data/lib/grit/tree.rb +123 -0
  38. data/lib/open3_detach.rb +46 -0
  39. data/test/bench/benchmarks.rb +126 -0
  40. data/test/helper.rb +18 -0
  41. data/test/profile.rb +21 -0
  42. data/test/suite.rb +6 -0
  43. data/test/test_actor.rb +35 -0
  44. data/test/test_blame.rb +32 -0
  45. data/test/test_blame_tree.rb +33 -0
  46. data/test/test_blob.rb +83 -0
  47. data/test/test_commit.rb +207 -0
  48. data/test/test_commit_stats.rb +33 -0
  49. data/test/test_commit_write.rb +20 -0
  50. data/test/test_config.rb +58 -0
  51. data/test/test_diff.rb +18 -0
  52. data/test/test_file_index.rb +56 -0
  53. data/test/test_git.rb +94 -0
  54. data/test/test_grit.rb +32 -0
  55. data/test/test_head.rb +72 -0
  56. data/test/test_index_status.rb +40 -0
  57. data/test/test_merge.rb +17 -0
  58. data/test/test_raw.rb +16 -0
  59. data/test/test_real.rb +19 -0
  60. data/test/test_reality.rb +17 -0
  61. data/test/test_remote.rb +14 -0
  62. data/test/test_repo.rb +381 -0
  63. data/test/test_rubygit.rb +192 -0
  64. data/test/test_rubygit_alt.rb +40 -0
  65. data/test/test_rubygit_index.rb +76 -0
  66. data/test/test_rubygit_iv2.rb +28 -0
  67. data/test/test_submodule.rb +69 -0
  68. data/test/test_tag.rb +103 -0
  69. data/test/test_tree.rb +101 -0
  70. metadata +143 -0
data/lib/grit/git.rb ADDED
@@ -0,0 +1,141 @@
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, self.work_tree = git_dir, work_tree
36
+ self.bytes_read = 0
37
+ end
38
+
39
+ def shell_escape(str)
40
+ str.to_s.gsub("'", "\\\\'").gsub(";", '\\;')
41
+ end
42
+ alias_method :e, :shell_escape
43
+
44
+ # Run the given git command with the specified arguments and return
45
+ # the result as a String
46
+ # +cmd+ is the command
47
+ # +options+ is a hash of Ruby style options
48
+ # +args+ is the list of arguments (to be joined by spaces)
49
+ #
50
+ # Examples
51
+ # git.rev_list({:max_count => 10, :header => true}, "master")
52
+ #
53
+ # Returns String
54
+ def method_missing(cmd, options = {}, *args)
55
+ run('', cmd, '', options, args)
56
+ end
57
+
58
+ def run(prefix, cmd, postfix, options, args)
59
+ timeout = options.delete(:timeout) rescue nil
60
+ timeout = true if timeout.nil?
61
+
62
+ opt_args = transform_options(options)
63
+ ext_args = args.reject { |a| a.empty? }.map { |a| (a == '--' || a[0].chr == '|') ? a : "'#{e(a)}'" }
64
+
65
+ work_tree_option = " --work-tree='#{self.work_tree}'" if self.work_tree
66
+ call = "#{prefix}#{Git.git_binary}#{work_tree_option} --git-dir='#{self.git_dir}' #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{e(postfix)}"
67
+ Grit.log(call) if Grit.debug
68
+ response, err = timeout ? sh(call) : wild_sh(call)
69
+ Grit.log(response) if Grit.debug
70
+ Grit.log(err) if Grit.debug
71
+ response
72
+ end
73
+
74
+ def sh(command)
75
+ ret, err = '', ''
76
+ Open3.popen3(command) do |_, stdout, stderr|
77
+ Timeout.timeout(self.class.git_timeout) do
78
+ while tmp = stdout.read(1024)
79
+ ret += tmp
80
+ if (@bytes_read += tmp.size) > self.class.git_max_size
81
+ bytes = @bytes_read
82
+ @bytes_read = 0
83
+ raise GitTimeout.new(command, bytes)
84
+ end
85
+ end
86
+ end
87
+
88
+ while tmp = stderr.read(1024)
89
+ err += tmp
90
+ end
91
+ end
92
+ [ret, err]
93
+ rescue Timeout::Error, Grit::Git::GitTimeout
94
+ bytes = @bytes_read
95
+ @bytes_read = 0
96
+ raise GitTimeout.new(command, bytes)
97
+ end
98
+
99
+ def wild_sh(command)
100
+ ret, err = '', ''
101
+ Open3.popen3(command) do |_, stdout, stderr|
102
+ while tmp = stdout.read(1024)
103
+ ret += tmp
104
+ end
105
+
106
+ while tmp = stderr.read(1024)
107
+ err += tmp
108
+ end
109
+ end
110
+ [ret, err]
111
+ end
112
+
113
+ # Transform Ruby style options into git command line options
114
+ # +options+ is a hash of Ruby style options
115
+ #
116
+ # Returns String[]
117
+ # e.g. ["--max-count=10", "--header"]
118
+ def transform_options(options)
119
+ args = []
120
+ options.keys.each do |opt|
121
+ if opt.to_s.size == 1
122
+ if options[opt] == true
123
+ args << "-#{opt}"
124
+ else
125
+ val = options.delete(opt)
126
+ args << "-#{opt.to_s} '#{e(val)}'"
127
+ end
128
+ else
129
+ if options[opt] == true
130
+ args << "--#{opt.to_s.gsub(/_/, '-')}"
131
+ else
132
+ val = options.delete(opt)
133
+ args << "--#{opt.to_s.gsub(/_/, '-')}='#{e(val)}'"
134
+ end
135
+ end
136
+ end
137
+ args
138
+ end
139
+ end # Git
140
+
141
+ 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,177 @@
1
+ module Grit
2
+
3
+ class Ref
4
+
5
+ class << self
6
+ # Creates a new Ref object with <tt>name</tt> in the repository if it
7
+ # does not exist already. This method can only be called on Tag, Head or
8
+ # Remote.
9
+ #
10
+ # The <tt>startpoint</tt> defaults to HEAD.
11
+ #
12
+ # Returns kind of Ref (baked).
13
+ def create(repo, ref_name, startpoint = nil, type = nil)
14
+ type = extract_type type
15
+ startpoint = startpoint_from_object repo, startpoint
16
+
17
+ path = File.join repo.path, %W[ refs #{ type }s #{ ref_name } ]
18
+ unless File.exists? path
19
+ dir = File.dirname path
20
+ FileUtils.mkdir_p dir unless File.exist? dir
21
+ open(path, 'w') { |f| f << startpoint }
22
+ end
23
+
24
+ new ref_name, Commit.create(repo, :id => startpoint)
25
+ end
26
+
27
+ # Find all Refs
28
+ # +repo+ is the Repo
29
+ # +options+ is a Hash of options
30
+ #
31
+ # Returns Grit::Ref[] (baked)
32
+ def find_all(repo, options = {})
33
+ refs = []
34
+ already = {}
35
+ Dir.chdir(repo.path) do
36
+ files = Dir.glob(prefix + '/**/*')
37
+ files.each do |ref|
38
+ next if !File.file?(ref)
39
+ id = File.read(ref).chomp
40
+ name = ref.sub("#{prefix}/", '')
41
+ commit = Commit.create(repo, :id => id)
42
+ if !already[name]
43
+ refs << self.new(name, commit)
44
+ already[name] = true
45
+ end
46
+ end
47
+
48
+ if File.file?('packed-refs')
49
+ File.readlines('packed-refs').each do |line|
50
+ if m = /^(\w{40}) (.*?)$/.match(line)
51
+ next if !Regexp.new('^' + prefix).match(m[2])
52
+ name = m[2].sub("#{prefix}/", '')
53
+ commit = Commit.create(repo, :id => m[1])
54
+ if !already[name]
55
+ refs << self.new(name, commit)
56
+ already[name] = true
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ refs
64
+ end
65
+
66
+ protected
67
+ def extract_type(type)
68
+ type ||= name.split('::').last.downcase
69
+
70
+ %w[ head remote tag ].include? type or
71
+ raise ArgumentError, "expected head, remote or tags but was #{type}"
72
+
73
+ type
74
+ end
75
+ def startpoint_from_object(repo, object)
76
+ case object
77
+ when String
78
+ ref = repo.refs.find {|r| r.name == object }
79
+ ref ? ref.commit.id : object
80
+ when Grit::Ref; object.commit.id
81
+ when Grit::Commit; object.id
82
+ else
83
+ repo.git.rev_parse nil, 'HEAD'
84
+ end
85
+ end
86
+ def prefix
87
+ "refs/#{name.to_s.gsub(/^.*::/, '').downcase}s"
88
+ end
89
+
90
+ end
91
+
92
+ attr_reader :name
93
+ attr_reader :commit
94
+
95
+ # Instantiate a new Head
96
+ # +name+ is the name of the head
97
+ # +commit+ is the Commit that the head points to
98
+ #
99
+ # Returns Grit::Head (baked)
100
+ def initialize(name, commit)
101
+ @name, @commit = name, commit
102
+ @repo = @commit.instance_variable_get :@repo
103
+ @git = @repo.git
104
+ end
105
+
106
+ # Checkout the current Ref unless the current repo is bare. In this case
107
+ # a RuntimeError is raised.
108
+ def checkout
109
+ # TODO: should this change the HEAD?
110
+ raise RuntimeError, 'bare repository' if @repo.bare
111
+ invoke :checkout, @name
112
+ end
113
+
114
+ # Pretty object inspection
115
+ def inspect
116
+ %Q{#<#{self.class.name} "#{@name}">}
117
+ end
118
+
119
+ def ==(other)
120
+ self.class === other and name == other.name
121
+ end
122
+
123
+ protected
124
+ def invoke(cmd, *args)
125
+ @git.send cmd, {}, *args
126
+ end
127
+ end # Ref
128
+
129
+ # A Head is a named reference to a Commit. Every Head instance contains a
130
+ # name and a Commit object.
131
+ #
132
+ # r = Grit::Repo.new("/path/to/repo")
133
+ # h = r.heads.first
134
+ # h.name # => "master"
135
+ # h.commit # => #<Grit::Commit "1c09...">
136
+ # h.commit.id # => "1c09f116cbc2cb4100fb6935bb162daa4723f455"
137
+ class Head < Ref
138
+
139
+ # Get the HEAD revision of the repo.
140
+ # +repo+ is the Repo
141
+ # +options+ is a Hash of options
142
+ #
143
+ # Returns Grit::Head (baked)
144
+ def self.current(repo, options = {})
145
+ head = File.read File.join(repo.path, 'HEAD')
146
+ head.chomp!
147
+
148
+ if match = /ref: refs\/heads\/(.*)/.match(head)
149
+ commit = Commit.create repo,
150
+ :id => repo.git.rev_parse(options, 'HEAD')
151
+
152
+ new match[1], commit
153
+ end
154
+ end
155
+
156
+ # Checkout the this branch, do stuff and ensure that the old branch gets
157
+ # checked out afterwards.
158
+ # If message is set the index is commited. In this case the commit (baked)
159
+ # is returned.
160
+ def in_branch(message = nil)
161
+ old_ref = @repo.head
162
+ checkout
163
+ yield @repo
164
+
165
+ if message
166
+ @repo.commit_index message
167
+ @repo.head.commit
168
+ end
169
+ ensure
170
+ old_ref.checkout
171
+ end
172
+
173
+ end # Head
174
+
175
+ class Remote < Ref; end
176
+
177
+ end # Grit