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
data/lib/grit/git.rb CHANGED
@@ -19,6 +19,8 @@ module Grit
19
19
 
20
20
  undef_method :clone
21
21
 
22
+ include GitRuby
23
+
22
24
  class << self
23
25
  attr_accessor :git_binary, :git_timeout
24
26
  end
@@ -44,31 +46,41 @@ module Grit
44
46
  #
45
47
  # Returns String
46
48
  def method_missing(cmd, options = {}, *args)
49
+ run('', cmd, '', options, args)
50
+ end
51
+
52
+ def run(prefix, cmd, postfix, options, args)
47
53
  timeout = options.delete(:timeout)
48
54
  timeout = true if timeout.nil?
49
55
 
50
56
  opt_args = transform_options(options)
51
57
  ext_args = args.map { |a| a == '--' ? a : "'#{a}'" }
52
58
 
53
- call = "#{Git.git_binary} --git-dir='#{self.git_dir}' #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}"
54
- puts call if Grit.debug
55
- response = timeout ? sh(call) : wild_sh(call)
56
- puts response if Grit.debug
59
+ call = "#{prefix}#{Git.git_binary} --git-dir='#{self.git_dir}' #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{postfix}"
60
+ Grit.log(call) if Grit.debug
61
+ response, err = timeout ? sh(call) : wild_sh(call)
62
+ Grit.log(response) if Grit.debug
63
+ Grit.log(err) if Grit.debug
57
64
  response
58
65
  end
59
66
 
60
67
  def sh(command)
61
- pid, _, io, _ = Open4.popen4(command)
62
- ret = Timeout.timeout(self.class.git_timeout) { io.read }
63
- @bytes_read += ret.size
68
+ ret, pid, err = nil, nil, nil
69
+ Open4.popen4(command) do |id, _, stdout, stderr|
70
+ pid = id
71
+ ret = Timeout.timeout(self.class.git_timeout) { stdout.read }
72
+ err = stderr.read
73
+ @bytes_read += ret.size
64
74
 
65
- if @bytes_read > 5242880 # 5.megabytes
66
- bytes = @bytes_read
67
- @bytes_read = 0
68
- raise GitTimeout.new(command, bytes)
75
+ if @bytes_read > 5242880 # 5.megabytes
76
+ bytes = @bytes_read
77
+ @bytes_read = 0
78
+ raise GitTimeout.new(command, bytes)
79
+ end
69
80
  end
70
-
71
- ret
81
+ [ret, err]
82
+ rescue Errno::ECHILD
83
+ [ret, err]
72
84
  rescue Object => e
73
85
  Process.kill('KILL', pid) rescue nil
74
86
  bytes = @bytes_read
@@ -77,8 +89,14 @@ module Grit
77
89
  end
78
90
 
79
91
  def wild_sh(command)
80
- pid, _, io, _ = Open4.popen4(command)
81
- io.read
92
+ ret, err = nil, nil
93
+ Open4.popen4(command) {|pid, _, stdout, stderr|
94
+ ret = stdout.read
95
+ err = stderr.read
96
+ }
97
+ [ret, err]
98
+ rescue Errno::ECHILD
99
+ [ret, err]
82
100
  end
83
101
 
84
102
  # Transform Ruby style options into git command line options
data/lib/grit/head.rb CHANGED
@@ -29,8 +29,11 @@ module Grit
29
29
  #
30
30
  # Returns Grit::Head[] (baked)
31
31
  def self.find_all(repo, options = {})
32
- default_options = {:sort => "committerdate",
33
- :format => "%(refname)%00%(objectname)"}
32
+ default_options = {
33
+ :sort => "committerdate",
34
+ :format => "%(refname)%00%(objectname)",
35
+ :timeout => false
36
+ }
34
37
 
35
38
  actual_options = default_options.merge(options)
36
39
 
@@ -38,19 +41,7 @@ module Grit
38
41
 
39
42
  self.list_from_string(repo, output)
40
43
  end
41
-
42
- # Get the HEAD revision of the repo.
43
- # +repo+ is the Repo
44
- # +options+ is a Hash of options
45
- #
46
- # Returns Grit::Head (baked)
47
- def self.current(repo, options = {})
48
- head = File.open(File.join(repo.path, 'HEAD')).read.chomp
49
- if /ref: refs\/heads\/(.*)/.match(head)
50
- self.new($1, repo.git.rev_parse(options, 'HEAD'))
51
- end
52
- end
53
-
44
+
54
45
  # Parse out head information into an array of baked head objects
55
46
  # +repo+ is the Repo
56
47
  # +text+ is the text output from the git command
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/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
data/lib/grit/repo.rb CHANGED
@@ -5,6 +5,7 @@ module Grit
5
5
 
6
6
  # The path of the git repo as a String
7
7
  attr_accessor :path
8
+ attr_accessor :working_dir
8
9
  attr_reader :bare
9
10
 
10
11
  # The git command line interface object
@@ -18,13 +19,14 @@ module Grit
18
19
  # g = Repo.new("/Users/tom/public/grit.git")
19
20
  #
20
21
  # Returns Grit::Repo
21
- def initialize(path)
22
+ def initialize(path, options = {})
22
23
  epath = File.expand_path(path)
23
24
 
24
25
  if File.exist?(File.join(epath, '.git'))
26
+ self.working_dir = epath
25
27
  self.path = File.join(epath, '.git')
26
28
  @bare = false
27
- elsif File.exist?(epath) && epath =~ /\.git$/
29
+ elsif File.exist?(epath) && (epath =~ /\.git$/ || options[:is_bare])
28
30
  self.path = epath
29
31
  @bare = true
30
32
  elsif File.exist?(epath)
@@ -36,6 +38,13 @@ module Grit
36
38
  self.git = Git.new(self.path)
37
39
  end
38
40
 
41
+ def self.init(path)
42
+ # !! TODO !!
43
+ # create directory
44
+ # generate initial git directory
45
+ # create new Grit::Repo on that dir, return it
46
+ end
47
+
39
48
  # The project's description. Taken verbatim from GIT_REPO/description
40
49
  #
41
50
  # Returns String
@@ -53,6 +62,10 @@ module Grit
53
62
 
54
63
  alias_method :branches, :heads
55
64
 
65
+ def is_head?(head_name)
66
+ heads.find { |h| h.name == head_name }
67
+ end
68
+
56
69
  # Object reprsenting the current repo head.
57
70
  #
58
71
  # Returns Grit::Head (baked)
@@ -60,6 +73,47 @@ module Grit
60
73
  Head.current(self)
61
74
  end
62
75
 
76
+
77
+ # Commits current index
78
+ #
79
+ # Returns true/false if commit worked
80
+ def commit_index(message)
81
+ self.git.commit({}, '-m', message)
82
+ end
83
+
84
+ # Commits all tracked and modified files
85
+ #
86
+ # Returns true/false if commit worked
87
+ def commit_all(message)
88
+ self.git.commit({}, '-a', '-m', message)
89
+ end
90
+
91
+ # Adds files to the index
92
+ def add(*files)
93
+ self.git.add({}, *files.flatten)
94
+ end
95
+
96
+ # Adds files to the index
97
+ def remove(*files)
98
+ self.git.rm({}, *files.flatten)
99
+ end
100
+
101
+
102
+ def blame_tree(commit, path = nil)
103
+ commit_array = self.git.blame_tree(commit, path)
104
+
105
+ final_array = {}
106
+ commit_array.each do |file, sha|
107
+ final_array[file] = commit(sha)
108
+ end
109
+ final_array
110
+ end
111
+
112
+ def status
113
+ Status.new(self)
114
+ end
115
+
116
+
63
117
  # An array of Tag objects that are available in this repo
64
118
  #
65
119
  # Returns Grit::Tag[] (baked)
@@ -67,9 +121,32 @@ module Grit
67
121
  Tag.find_all(self)
68
122
  end
69
123
 
124
+ # An array of Remote objects representing the remote branches in
125
+ # this repo
126
+ #
127
+ # Returns Grit::Remote[] (baked)
128
+ def remotes
129
+ Remote.find_all(self)
130
+ end
131
+
132
+ # An array of Ref objects representing the refs in
133
+ # this repo
134
+ #
135
+ # Returns Grit::Ref[] (baked)
136
+ def refs
137
+ [ Head.find_all(self), Tag.find_all(self), Remote.find_all(self) ].flatten
138
+ end
139
+
140
+ def commit_stats(start = 'master', max_count = 10, skip = 0)
141
+ options = {:max_count => max_count,
142
+ :skip => skip}
143
+
144
+ CommitStats.find_all(self, start, options)
145
+ end
146
+
70
147
  # An array of Commit objects representing the history of a given ref/commit
71
148
  # +start+ is the branch/commit name (default 'master')
72
- # +max_count+ is the maximum number of commits to return (default 10)
149
+ # +max_count+ is the maximum number of commits to return (default 10, use +false+ for all)
73
150
  # +skip+ is the number of commits to skip (default 0)
74
151
  #
75
152
  # Returns Grit::Commit[] (baked)
@@ -176,10 +253,10 @@ module Grit
176
253
  # Grit::Repo.init_bare('/var/git/myrepo.git')
177
254
  #
178
255
  # Returns Grit::Repo (the newly created repo)
179
- def self.init_bare(path, options = {})
256
+ def self.init_bare(path, git_options = {}, repo_options = {})
180
257
  git = Git.new(path)
181
- git.init(options)
182
- self.new(path)
258
+ git.init(git_options)
259
+ self.new(path, repo_options)
183
260
  end
184
261
 
185
262
  # Fork a bare git repository from this repo
@@ -235,6 +312,14 @@ module Grit
235
312
  options[:prefix] = prefix if prefix
236
313
  self.git.archive(options, treeish, "| gzip")
237
314
  end
315
+
316
+ # run archive directly to a file
317
+ def archive_to_file(treeish = 'master', prefix = nil, filename = 'archive.tar.gz')
318
+ options = {}
319
+ options[:prefix] = prefix if prefix
320
+ self.git.archive(options, treeish, "| gzip > #{filename}")
321
+ end
322
+
238
323
 
239
324
  # Enable git-daemon serving of this repository by writing the
240
325
  # git-daemon-export-ok file to its git directory
@@ -289,6 +374,10 @@ module Grit
289
374
  @config ||= Config.new(self)
290
375
  end
291
376
 
377
+ def index
378
+ Index.new(self)
379
+ end
380
+
292
381
  # Pretty object inspection
293
382
  def inspect
294
383
  %Q{#<Grit::Repo "#{@path}">}