gifts 0.0.1

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 (56) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +29 -0
  5. data/Rakefile +9 -0
  6. data/gifts.gemspec +32 -0
  7. data/lib/gifts.rb +22 -0
  8. data/lib/gifts/commit_table.rb +70 -0
  9. data/lib/gifts/database.rb +49 -0
  10. data/lib/gifts/diff_table.rb +55 -0
  11. data/lib/gifts/file_table.rb +51 -0
  12. data/lib/gifts/repo_table.rb +31 -0
  13. data/lib/gifts/table_base.rb +18 -0
  14. data/lib/gifts/term_table.rb +28 -0
  15. data/lib/gifts/user_table.rb +19 -0
  16. data/lib/gifts/version.rb +3 -0
  17. data/spec/.gitkeeper +0 -0
  18. data/vendor/lib/LICENSE-grit +22 -0
  19. data/vendor/lib/LICENSE-grit_ext +22 -0
  20. data/vendor/lib/gifts/grit.rb +73 -0
  21. data/vendor/lib/gifts/grit/actor.rb +52 -0
  22. data/vendor/lib/gifts/grit/blame.rb +70 -0
  23. data/vendor/lib/gifts/grit/blob.rb +126 -0
  24. data/vendor/lib/gifts/grit/commit.rb +324 -0
  25. data/vendor/lib/gifts/grit/commit_stats.rb +128 -0
  26. data/vendor/lib/gifts/grit/config.rb +44 -0
  27. data/vendor/lib/gifts/grit/diff.rb +97 -0
  28. data/vendor/lib/gifts/grit/errors.rb +10 -0
  29. data/vendor/lib/gifts/grit/git-ruby.rb +262 -0
  30. data/vendor/lib/gifts/grit/git-ruby/commit_db.rb +52 -0
  31. data/vendor/lib/gifts/grit/git-ruby/git_object.rb +353 -0
  32. data/vendor/lib/gifts/grit/git-ruby/internal/file_window.rb +58 -0
  33. data/vendor/lib/gifts/grit/git-ruby/internal/loose.rb +137 -0
  34. data/vendor/lib/gifts/grit/git-ruby/internal/pack.rb +398 -0
  35. data/vendor/lib/gifts/grit/git-ruby/internal/raw_object.rb +44 -0
  36. data/vendor/lib/gifts/grit/git-ruby/repository.rb +784 -0
  37. data/vendor/lib/gifts/grit/git.rb +501 -0
  38. data/vendor/lib/gifts/grit/index.rb +222 -0
  39. data/vendor/lib/gifts/grit/lazy.rb +35 -0
  40. data/vendor/lib/gifts/grit/merge.rb +45 -0
  41. data/vendor/lib/gifts/grit/ref.rb +98 -0
  42. data/vendor/lib/gifts/grit/repo.rb +722 -0
  43. data/vendor/lib/gifts/grit/ruby1.9.rb +15 -0
  44. data/vendor/lib/gifts/grit/status.rb +153 -0
  45. data/vendor/lib/gifts/grit/submodule.rb +88 -0
  46. data/vendor/lib/gifts/grit/tag.rb +97 -0
  47. data/vendor/lib/gifts/grit/tree.rb +125 -0
  48. data/vendor/lib/gifts/grit_ext.rb +41 -0
  49. data/vendor/lib/gifts/grit_ext/actor.rb +15 -0
  50. data/vendor/lib/gifts/grit_ext/blob.rb +26 -0
  51. data/vendor/lib/gifts/grit_ext/commit.rb +15 -0
  52. data/vendor/lib/gifts/grit_ext/diff.rb +23 -0
  53. data/vendor/lib/gifts/grit_ext/tag.rb +10 -0
  54. data/vendor/lib/gifts/grit_ext/tree.rb +10 -0
  55. data/vendor/lib/gifts/grit_ext/version.rb +7 -0
  56. metadata +256 -0
@@ -0,0 +1,222 @@
1
+ module Gifts::Grit
2
+
3
+ class Index
4
+ # Public: Gets/Sets the Gifts::Grit::Repo to which this index belongs.
5
+ attr_accessor :repo
6
+
7
+ # Public: Gets/Sets the Hash tree map that holds the changes to be made
8
+ # in the next commit.
9
+ attr_accessor :tree
10
+
11
+ # Public: Gets/Sets the Gifts::Grit::Tree object representing the tree upon
12
+ # which the next commit will be based.
13
+ attr_accessor :current_tree
14
+
15
+ # Public: if a tree or commit is written, this stores the size of that object
16
+ attr_reader :last_tree_size
17
+ attr_reader :last_commit_size
18
+
19
+ # Initialize a new Index object.
20
+ #
21
+ # repo - The Gifts::Grit::Repo to which the index belongs.
22
+ #
23
+ # Returns the newly initialized Gifts::Grit::Index.
24
+ def initialize(repo)
25
+ self.repo = repo
26
+ self.tree = {}
27
+ self.current_tree = nil
28
+ end
29
+
30
+ # Public: Add a file to the index.
31
+ #
32
+ # path - The String file path including filename (no slash prefix).
33
+ # data - The String binary contents of the file.
34
+ #
35
+ # Returns nothing.
36
+ def add(path, data)
37
+ path = path.split('/')
38
+ filename = path.pop
39
+
40
+ current = self.tree
41
+
42
+ path.each do |dir|
43
+ current[dir] ||= {}
44
+ node = current[dir]
45
+ current = node
46
+ end
47
+
48
+ current[filename] = data
49
+ end
50
+
51
+ # Public: Delete the given file from the index.
52
+ #
53
+ # path - The String file path including filename (no slash prefix).
54
+ #
55
+ # Returns nothing.
56
+ def delete(path)
57
+ add(path, false)
58
+ end
59
+
60
+ # Public: Read the contents of the given Tree into the index to use as a
61
+ # starting point for the index.
62
+ #
63
+ # tree - The String branch/tag/sha of the Git tree object.
64
+ #
65
+ # Returns nothing.
66
+ def read_tree(tree)
67
+ self.current_tree = self.repo.tree(tree)
68
+ end
69
+
70
+ # Public: Commit the contents of the index. This method supports two
71
+ # formats for arguments:
72
+ #
73
+ # message - The String commit message.
74
+ # options - An optional Hash of index options.
75
+ # :parents - Array of String commit SHA1s or Gifts::Grit::Commit
76
+ # objects to attach this commit to to form a
77
+ # new head (default: nil).
78
+ # :actor - The Gifts::Grit::Actor details of the user making
79
+ # the commit (default: nil).
80
+ # :last_tree - The String SHA1 of a tree to compare with
81
+ # in order to avoid making empty commits
82
+ # (default: nil).
83
+ # :head - The String branch name to write this head to
84
+ # (default: nil).
85
+ # :committed_date - The Time that the commit was made.
86
+ # (Default: Time.now)
87
+ # :authored_date - The Time that the commit was authored.
88
+ # (Default: committed_date)
89
+ #
90
+ # The legacy argument style looks like:
91
+ #
92
+ # message - The String commit message.
93
+ # parents - Array of String commit SHA1s or Gifts::Grit::Commit objects to
94
+ # attach this commit to to form a new head (default: nil).
95
+ # actor - The Gifts::Grit::Actor details of the user making the commit
96
+ # (default: nil).
97
+ # last_tree - The String SHA1 of a tree to compare with in order to avoid
98
+ # making empty commits (default: nil).
99
+ # head - The String branch name to write this head to
100
+ # (default: "master").
101
+ #
102
+ # Returns a String of the SHA1 of the new commit.
103
+ def commit(message, parents = nil, actor = nil, last_tree = nil, head = 'master')
104
+ commit_tree_sha = nil
105
+ if parents.is_a?(Hash)
106
+ commit_tree_sha = parents[:commit_tree_sha]
107
+ actor = parents[:actor]
108
+ committer = parents[:committer]
109
+ author = parents[:author]
110
+ last_tree = parents[:last_tree]
111
+ head = parents[:head]
112
+ committed_date = parents[:committed_date]
113
+ authored_date = parents[:authored_date]
114
+ parents = parents[:parents]
115
+ end
116
+
117
+ committer ||= actor
118
+ author ||= committer
119
+
120
+ if commit_tree_sha
121
+ tree_sha1 = commit_tree_sha
122
+ else
123
+ tree_sha1 = write_tree(self.tree, self.current_tree)
124
+ end
125
+
126
+ # don't write identical commits
127
+ return false if tree_sha1 == last_tree
128
+
129
+ contents = []
130
+ contents << ['tree', tree_sha1].join(' ')
131
+ parents.each do |p|
132
+ contents << ['parent', p].join(' ')
133
+ end if parents
134
+
135
+ committer ||= begin
136
+ config = Config.new(self.repo)
137
+ Actor.new(config['user.name'], config['user.email'])
138
+ end
139
+ author ||= committer
140
+ committed_date ||= Time.now
141
+ authored_date ||= committed_date
142
+
143
+ contents << ['author', author.output(authored_date)].join(' ')
144
+ contents << ['committer', committer.output(committed_date)].join(' ')
145
+ contents << ''
146
+ contents << message
147
+
148
+ contents = contents.join("\n")
149
+ @last_commit_size = contents.size
150
+ commit_sha1 = self.repo.git.put_raw_object(contents, 'commit')
151
+
152
+ self.repo.update_ref(head, commit_sha1) if head
153
+ commit_sha1
154
+ end
155
+
156
+ # Recursively write a tree to the index.
157
+ #
158
+ # tree - The Hash tree map:
159
+ # key - The String directory or filename.
160
+ # val - The Hash submap or the String contents of the file.
161
+ # now_tree - The Gifts::Grit::Tree representing the a previous tree upon which
162
+ # this tree will be based (default: nil).
163
+ #
164
+ # Returns the String SHA1 String of the tree.
165
+ def write_tree(tree = nil, now_tree = nil)
166
+ tree = self.tree if !tree
167
+ tree_contents = {}
168
+
169
+ # fill in original tree
170
+ now_tree = read_tree(now_tree) if(now_tree && now_tree.is_a?(String))
171
+ now_tree.contents.each do |obj|
172
+ sha = [obj.id].pack("H*")
173
+ k = obj.name
174
+ k += '/' if (obj.class == Gifts::Grit::Tree)
175
+ tmode = obj.mode.to_i.to_s ## remove zero-padding
176
+ tree_contents[k] = "%s %s\0%s" % [tmode, obj.name, sha]
177
+ end if now_tree
178
+
179
+ # overwrite with new tree contents
180
+ tree.each do |k, v|
181
+ case v
182
+ when Array
183
+ sha, mode = v
184
+ if sha.size == 40 # must be a sha
185
+ sha = [sha].pack("H*")
186
+ mode = mode.to_i.to_s # leading 0s not allowed
187
+ k = k.split('/').last # slashes not allowed
188
+ str = "%s %s\0%s" % [mode, k, sha]
189
+ tree_contents[k] = str
190
+ end
191
+ when String
192
+ sha = write_blob(v)
193
+ sha = [sha].pack("H*")
194
+ str = "%s %s\0%s" % ['100644', k, sha]
195
+ tree_contents[k] = str
196
+ when Hash
197
+ ctree = now_tree/k if now_tree
198
+ sha = write_tree(v, ctree)
199
+ sha = [sha].pack("H*")
200
+ str = "%s %s\0%s" % ['40000', k, sha]
201
+ tree_contents[k + '/'] = str
202
+ when false
203
+ tree_contents.delete(k)
204
+ end
205
+ end
206
+
207
+ tr = tree_contents.sort.map { |k, v| v }.join('')
208
+ @last_tree_size = tr.size
209
+ self.repo.git.put_raw_object(tr, 'tree')
210
+ end
211
+
212
+ # Write a blob to the index.
213
+ #
214
+ # data - The String data to write.
215
+ #
216
+ # Returns the String SHA1 of the new blob.
217
+ def write_blob(data)
218
+ self.repo.git.put_raw_object(data, 'blob')
219
+ end
220
+ end # Index
221
+
222
+ end # Gifts::Grit
@@ -0,0 +1,35 @@
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 self.extended(klass)
20
+ klass.send(:attr_writer, :lazy_source)
21
+ end
22
+
23
+ def lazy_reader(*args)
24
+ args.each do |arg|
25
+ ivar = "@#{arg}"
26
+ define_method(arg) do
27
+ if instance_variable_defined?(ivar)
28
+ val = instance_variable_get(ivar)
29
+ return val if val
30
+ end
31
+ instance_variable_set(ivar, (@lazy_source ||= lazy_source).send(arg))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ module Gifts::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{#<Gifts::Grit::Merge}
42
+ end
43
+ end # Merge
44
+
45
+ end # Gifts::Grit
@@ -0,0 +1,98 @@
1
+ module Gifts::Grit
2
+
3
+ class Ref
4
+
5
+ class << self
6
+
7
+ # Count all Refs
8
+ # +repo+ is the Repo
9
+ # +options+ is a Hash of options
10
+ #
11
+ # Returns int
12
+ def count_all(repo, options = {})
13
+ refs = repo.git.refs(options, prefix)
14
+ refs.split("\n").size
15
+ end
16
+
17
+ # Find all Refs
18
+ # +repo+ is the Repo
19
+ # +options+ is a Hash of options
20
+ #
21
+ # Returns Gifts::Grit::Ref[] (baked)
22
+ def find_all(repo, options = {})
23
+ refs = repo.git.refs(options, prefix)
24
+ refs.split("\n").map do |ref|
25
+ name, id = *ref.split(' ')
26
+ self.new(name, repo, id)
27
+ end
28
+ end
29
+
30
+ protected
31
+
32
+ def prefix
33
+ "refs/#{name.to_s.gsub(/^.*::/, '').downcase}s"
34
+ end
35
+
36
+ end
37
+
38
+ attr_reader :name
39
+
40
+ # Instantiate a new Head
41
+ # +name+ is the name of the head
42
+ # +commit+ is the Commit that the head points to
43
+ #
44
+ # Returns Gifts::Grit::Head (baked)
45
+ def initialize(name, repo, commit_id)
46
+ @name = name
47
+ @commit_id = commit_id
48
+ @repo_ref = repo
49
+ @commit = nil
50
+ end
51
+
52
+ def commit
53
+ @commit ||= get_commit
54
+ end
55
+
56
+ # Pretty object inspection
57
+ def inspect
58
+ %Q{#<#{self.class.name} "#{@name}">}
59
+ end
60
+
61
+ protected
62
+
63
+ def get_commit
64
+ Commit.create(@repo_ref, :id => @commit_id)
65
+ end
66
+
67
+ end # Ref
68
+
69
+ # A Head is a named reference to a Commit. Every Head instance contains a name
70
+ # and a Commit object.
71
+ #
72
+ # r = Gifts::Grit::Repo.new("/path/to/repo")
73
+ # h = r.heads.first
74
+ # h.name # => "master"
75
+ # h.commit # => #<Gifts::Grit::Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
76
+ # h.commit.id # => "1c09f116cbc2cb4100fb6935bb162daa4723f455"
77
+ class Head < Ref
78
+
79
+ # Get the HEAD revision of the repo.
80
+ # +repo+ is the Repo
81
+ # +options+ is a Hash of options
82
+ #
83
+ # Returns Gifts::Grit::Head (baked)
84
+ def self.current(repo, options = {})
85
+ head = repo.git.fs_read('HEAD').chomp
86
+ if /ref: refs\/heads\/(.*)/.match(head)
87
+ id = repo.git.rev_parse(options, 'HEAD')
88
+ self.new($1, repo, id)
89
+ end
90
+ end
91
+
92
+ end # Head
93
+
94
+ class Remote < Ref; end
95
+
96
+ class Note < Ref; end
97
+
98
+ end # Gifts::Grit
@@ -0,0 +1,722 @@
1
+ module Gifts::Grit
2
+
3
+ class Repo
4
+ DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
5
+ BATCH_PARSERS = {
6
+ 'commit' => Commit
7
+ }
8
+
9
+ # Public: The String path of the Git repo.
10
+ attr_accessor :path
11
+
12
+ # Public: The String path to the working directory of the repo, or nil if
13
+ # there is no working directory.
14
+ attr_accessor :working_dir
15
+
16
+ # Public: The Boolean of whether or not the repo is bare.
17
+ attr_reader :bare
18
+
19
+ # Public: The Gifts::Grit::Git command line interface object.
20
+ attr_accessor :git
21
+
22
+ # Public: Create a new Repo instance.
23
+ #
24
+ # path - The String path to either the root git directory or the bare
25
+ # git repo. Bare repos are expected to end with ".git".
26
+ # options - A Hash of options (default: {}):
27
+ # :is_bare - Boolean whether to consider the repo as bare even
28
+ # if the repo name does not end with ".git".
29
+ #
30
+ # Examples
31
+ #
32
+ # r = Repo.new("/Users/tom/dev/normal")
33
+ # r = Repo.new("/Users/tom/public/bare.git")
34
+ # r = Repo.new("/Users/tom/public/bare", {:is_bare => true})
35
+ #
36
+ # Returns a newly initialized Gifts::Grit::Repo.
37
+ # Raises Gifts::Grit::InvalidGitRepositoryError if the path exists but is not
38
+ # a Git repository.
39
+ # Raises Gifts::Grit::NoSuchPathError if the path does not exist.
40
+ def initialize(path, options = {})
41
+ epath = File.expand_path(path)
42
+
43
+ if File.exist?(File.join(epath, '.git'))
44
+ self.working_dir = epath
45
+ self.path = File.join(epath, '.git')
46
+ @bare = false
47
+ elsif File.exist?(epath) && (epath =~ /\.git$/ || options[:is_bare])
48
+ self.path = epath
49
+ @bare = true
50
+ elsif File.exist?(epath)
51
+ raise InvalidGitRepositoryError.new(epath)
52
+ else
53
+ raise NoSuchPathError.new(epath)
54
+ end
55
+
56
+ self.git = Git.new(self.path)
57
+ end
58
+
59
+ # Public: Initialize a git repository (create it on the filesystem). By
60
+ # default, the newly created repository will contain a working directory.
61
+ # If you would like to create a bare repo, use Gifts::Grit::Repo.init_bare.
62
+ #
63
+ # path - The String full path to the repo. Traditionally ends with
64
+ # "/<name>.git".
65
+ # git_options - A Hash of additional options to the git init command
66
+ # (default: {}).
67
+ # repo_options - A Hash of additional options to the Gifts::Grit::Repo.new call
68
+ # (default: {}).
69
+ #
70
+ # Examples
71
+ #
72
+ # Gifts::Grit::Repo.init('/var/git/myrepo.git')
73
+ #
74
+ # Returns the newly created Gifts::Grit::Repo.
75
+ def self.init(path, git_options = {}, repo_options = {})
76
+ git_options = {:base => false}.merge(git_options)
77
+ git = Git.new(path)
78
+ git.fs_mkdir('..')
79
+ git.init(git_options, path)
80
+ self.new(path, repo_options)
81
+ end
82
+
83
+ # Public: Initialize a bare git repository (create it on the filesystem).
84
+ #
85
+ # path - The String full path to the repo. Traditionally ends with
86
+ # "/<name>.git".
87
+ # git_options - A Hash of additional options to the git init command
88
+ # (default: {}).
89
+ # repo_options - A Hash of additional options to the Gifts::Grit::Repo.new call
90
+ # (default: {}).
91
+ #
92
+ # Examples
93
+ #
94
+ # Gifts::Grit::Repo.init_bare('/var/git/myrepo.git')
95
+ #
96
+ # Returns the newly created Gifts::Grit::Repo.
97
+ def self.init_bare(path, git_options = {}, repo_options = {})
98
+ git_options = {:bare => true}.merge(git_options)
99
+ git = Git.new(path)
100
+ git.fs_mkdir('..')
101
+ git.init(git_options)
102
+ repo_options = {:is_bare => true}.merge(repo_options)
103
+ self.new(path, repo_options)
104
+ end
105
+
106
+ # Public: Initialize a bare Git repository (create it on the filesystem)
107
+ # or, if the repo already exists, simply return it.
108
+ #
109
+ # path - The String full path to the repo. Traditionally ends with
110
+ # "/<name>.git".
111
+ # git_options - A Hash of additional options to the git init command
112
+ # (default: {}).
113
+ # repo_options - A Hash of additional options to the Gifts::Grit::Repo.new call
114
+ # (default: {}).
115
+ #
116
+ # Returns the new or existing Gifts::Grit::Repo.
117
+ def self.init_bare_or_open(path, git_options = {}, repo_options = {})
118
+ git = Git.new(path)
119
+
120
+ unless git.exist?
121
+ git.fs_mkdir(path)
122
+ git.init(git_options)
123
+ end
124
+
125
+ self.new(path, repo_options)
126
+ end
127
+
128
+ # Public: Create a bare fork of this repository.
129
+ #
130
+ # path - The String full path of where to create the new fork.
131
+ # Traditionally ends with "/<name>.git".
132
+ # options - The Hash of additional options to the git clone command.
133
+ # These options will be merged on top of the default Hash:
134
+ # {:bare => true, :shared => true}.
135
+ #
136
+ # Returns the newly forked Gifts::Grit::Repo.
137
+ def fork_bare(path, options = {})
138
+ default_options = {:bare => true, :shared => true}
139
+ real_options = default_options.merge(options)
140
+ Git.new(path).fs_mkdir('..')
141
+ self.git.clone(real_options, self.path, path)
142
+ Repo.new(path)
143
+ end
144
+
145
+ # Public: Fork a bare git repository from another repo.
146
+ #
147
+ # path - The String full path of the repo from which to fork..
148
+ # Traditionally ends with "/<name>.git".
149
+ # options - The Hash of additional options to the git clone command.
150
+ # These options will be merged on top of the default Hash:
151
+ # {:bare => true, :shared => true}.
152
+ #
153
+ # Returns the newly forked Gifts::Grit::Repo.
154
+ def fork_bare_from(path, options = {})
155
+ default_options = {:bare => true, :shared => true}
156
+ real_options = default_options.merge(options)
157
+ Git.new(self.path).fs_mkdir('..')
158
+ self.git.clone(real_options, path, self.path)
159
+ Repo.new(self.path)
160
+ end
161
+
162
+ # Public: Return the full Git objects from the given SHAs. Only Commit
163
+ # objects are parsed for now.
164
+ #
165
+ # *shas - Array of String SHAs.
166
+ #
167
+ # Returns an Array of Gifts::Grit objects (Gifts::Grit::Commit).
168
+ def batch(*shas)
169
+ shas.flatten!
170
+ text = git.native(:cat_file, {:batch => true, :input => (shas * "\n")})
171
+ parse_batch(text)
172
+ end
173
+
174
+ # Parses `git cat-file --batch` output, returning an array of Gifts::Grit objects.
175
+ #
176
+ # text - Raw String output.
177
+ #
178
+ # Returns an Array of Gifts::Grit objects (Gifts::Grit::Commit).
179
+ def parse_batch(text)
180
+ io = StringIO.new(text)
181
+ objects = []
182
+ while line = io.gets
183
+ sha, type, size = line.split(" ", 3)
184
+ parser = BATCH_PARSERS[type]
185
+ if type == 'missing' || !parser
186
+ io.seek(size.to_i + 1, IO::SEEK_CUR)
187
+ objects << nil
188
+ next
189
+ end
190
+
191
+ object = io.read(size.to_i + 1)
192
+ objects << parser.parse_batch(self, sha, size, object)
193
+ end
194
+ objects
195
+ end
196
+
197
+ # The project's description. Taken verbatim from GIT_REPO/description
198
+ #
199
+ # Returns String
200
+ def description
201
+ self.git.fs_read('description').chomp
202
+ end
203
+
204
+ def blame(file, commit = nil)
205
+ Blame.new(self, file, commit)
206
+ end
207
+
208
+ # An array of Head objects representing the branch heads in
209
+ # this repo
210
+ #
211
+ # Returns Gifts::Grit::Head[] (baked)
212
+ def heads
213
+ Head.find_all(self)
214
+ end
215
+
216
+ def head_count
217
+ Head.count_all(self)
218
+ end
219
+
220
+ alias_method :branches, :heads
221
+ alias_method :branch_count, :head_count
222
+
223
+ def get_head(head_name)
224
+ heads.find { |h| h.name == head_name }
225
+ end
226
+
227
+ def is_head?(head_name)
228
+ get_head(head_name)
229
+ end
230
+
231
+ # Object reprsenting the current repo head.
232
+ #
233
+ # Returns Gifts::Grit::Head (baked)
234
+ def head
235
+ Head.current(self)
236
+ end
237
+
238
+
239
+ # Commits current index
240
+ #
241
+ # Returns true/false if commit worked
242
+ def commit_index(message)
243
+ self.git.commit({}, '-m', message)
244
+ end
245
+
246
+ # Commits all tracked and modified files
247
+ #
248
+ # Returns true/false if commit worked
249
+ def commit_all(message)
250
+ self.git.commit({}, '-a', '-m', message)
251
+ end
252
+
253
+ # Adds files to the index
254
+ def add(*files)
255
+ self.git.add({}, *files.flatten)
256
+ end
257
+
258
+ # Remove files from the index
259
+ def remove(*files)
260
+ self.git.rm({}, *files.flatten)
261
+ end
262
+
263
+
264
+ def blame_tree(commit, path = nil)
265
+ commit_array = self.git.blame_tree(commit, path)
266
+
267
+ final_array = {}
268
+ commit_array.each do |file, sha|
269
+ final_array[file] = commit(sha)
270
+ end
271
+ final_array
272
+ end
273
+
274
+ def status
275
+ Status.new(self)
276
+ end
277
+
278
+
279
+ # An array of Tag objects that are available in this repo
280
+ #
281
+ # Returns Gifts::Grit::Tag[] (baked)
282
+ def tags
283
+ Tag.find_all(self)
284
+ end
285
+
286
+ def tag_count
287
+ Tag.count_all(self)
288
+ end
289
+
290
+ # Finds the most recent annotated tag name that is reachable from a commit.
291
+ #
292
+ # @repo.recent_tag_name('master')
293
+ # # => "v1.0-0-abcdef"
294
+ #
295
+ # committish - optional commit SHA, branch, or tag name.
296
+ # options - optional hash of options to pass to git.
297
+ # Default: {:always => true}
298
+ # :tags => true # use lightweight tags too.
299
+ # :abbrev => Integer # number of hex digits to form the unique
300
+ # name. Defaults to 7.
301
+ # :long => true # always output tag + commit sha
302
+ # # see `git describe` docs for more options.
303
+ #
304
+ # Returns the String tag name, or just the commit if no tag is
305
+ # found. If there have been updates since the tag was made, a
306
+ # suffix is added with the number of commits since the tag, and
307
+ # the abbreviated object name of the most recent commit.
308
+ # Returns nil if the committish value is not found.
309
+ def recent_tag_name(committish = nil, options = {})
310
+ value = git.describe({:always => true}.update(options), committish.to_s).to_s.strip
311
+ value.size.zero? ? nil : value
312
+ end
313
+
314
+ # An array of Remote objects representing the remote branches in
315
+ # this repo
316
+ #
317
+ # Returns Gifts::Grit::Remote[] (baked)
318
+ def remotes
319
+ Remote.find_all(self)
320
+ end
321
+
322
+ def remote_count
323
+ Remote.count_all(self)
324
+ end
325
+
326
+ def remote_list
327
+ self.git.list_remotes
328
+ end
329
+
330
+ def remote_add(name, url)
331
+ self.git.remote({}, 'add', name, url)
332
+ end
333
+
334
+ def remote_fetch(name)
335
+ self.git.fetch({}, name)
336
+ end
337
+
338
+ # takes an array of remote names and last pushed dates
339
+ # fetches from all of the remotes where the local fetch
340
+ # date is earlier than the passed date, then records the
341
+ # last fetched date
342
+ #
343
+ # { 'origin' => date,
344
+ # 'peter => date,
345
+ # }
346
+ def remotes_fetch_needed(remotes)
347
+ remotes.each do |remote, date|
348
+ # TODO: check against date
349
+ self.remote_fetch(remote)
350
+ end
351
+ end
352
+
353
+
354
+ # An array of Ref objects representing the refs in
355
+ # this repo
356
+ #
357
+ # Returns Gifts::Grit::Ref[] (baked)
358
+ def refs
359
+ [ Head.find_all(self), Tag.find_all(self), Remote.find_all(self) ].flatten
360
+ end
361
+
362
+ # returns an array of hashes representing all references
363
+ def refs_list
364
+ refs = self.git.for_each_ref
365
+ refarr = refs.split("\n").map do |line|
366
+ shatype, ref = line.split("\t")
367
+ sha, type = shatype.split(' ')
368
+ [ref, sha, type]
369
+ end
370
+ refarr
371
+ end
372
+
373
+ def delete_ref(ref)
374
+ self.git.native(:update_ref, {:d => true}, ref)
375
+ end
376
+
377
+ def commit_stats(start = 'master', max_count = 10, skip = 0)
378
+ options = {:max_count => max_count,
379
+ :skip => skip}
380
+
381
+ CommitStats.find_all(self, start, options)
382
+ end
383
+
384
+ # An array of Commit objects representing the history of a given ref/commit
385
+ # +start+ is the branch/commit name (default 'master')
386
+ # +max_count+ is the maximum number of commits to return (default 10, use +false+ for all)
387
+ # +skip+ is the number of commits to skip (default 0)
388
+ #
389
+ # Returns Gifts::Grit::Commit[] (baked)
390
+ def commits(start = 'master', max_count = 10, skip = 0)
391
+ options = {:max_count => max_count,
392
+ :skip => skip}
393
+
394
+ Commit.find_all(self, start, options)
395
+ end
396
+
397
+ # The Commits objects that are reachable via +to+ but not via +from+
398
+ # Commits are returned in chronological order.
399
+ # +from+ is the branch/commit name of the younger item
400
+ # +to+ is the branch/commit name of the older item
401
+ #
402
+ # Returns Gifts::Grit::Commit[] (baked)
403
+ def commits_between(from, to)
404
+ Commit.find_all(self, "#{from}..#{to}").reverse
405
+ end
406
+
407
+ def fast_forwardable?(to, from)
408
+ mb = self.git.native(:merge_base, {}, [to, from]).strip
409
+ mb == from
410
+ end
411
+
412
+ # The Commits objects that are newer than the specified date.
413
+ # Commits are returned in chronological order.
414
+ # +start+ is the branch/commit name (default 'master')
415
+ # +since+ is a string representing a date/time
416
+ # +extra_options+ is a hash of extra options
417
+ #
418
+ # Returns Gifts::Grit::Commit[] (baked)
419
+ def commits_since(start = 'master', since = '1970-01-01', extra_options = {})
420
+ options = {:since => since}.merge(extra_options)
421
+
422
+ Commit.find_all(self, start, options)
423
+ end
424
+
425
+ # The number of commits reachable by the given branch/commit
426
+ # +start+ is the branch/commit name (default 'master')
427
+ #
428
+ # Returns Integer
429
+ def commit_count(start = 'master')
430
+ Commit.count(self, start)
431
+ end
432
+
433
+ # The Commit object for the specified id
434
+ # +id+ is the SHA1 identifier of the commit
435
+ #
436
+ # Returns Gifts::Grit::Commit (baked)
437
+ def commit(id)
438
+ options = {:max_count => 1}
439
+
440
+ Commit.find_all(self, id, options).first
441
+ end
442
+
443
+ # Returns a list of commits that is in +other_repo+ but not in self
444
+ #
445
+ # Returns Gifts::Grit::Commit[]
446
+ def commit_deltas_from(other_repo, ref = "master", other_ref = "master")
447
+ # TODO: we should be able to figure out the branch point, rather than
448
+ # rev-list'ing the whole thing
449
+ repo_refs = self.git.rev_list({}, ref).strip.split("\n")
450
+ other_repo_refs = other_repo.git.rev_list({}, other_ref).strip.split("\n")
451
+
452
+ (other_repo_refs - repo_refs).map do |refn|
453
+ Commit.find_all(other_repo, refn, {:max_count => 1}).first
454
+ end
455
+ end
456
+
457
+ def objects(refs)
458
+ refs = refs.split(/\s+/) if refs.respond_to?(:to_str)
459
+ self.git.rev_list({:objects => true, :timeout => false}, *refs).
460
+ split("\n").map { |a| a[0, 40] }
461
+ end
462
+
463
+ def commit_objects(refs)
464
+ refs = refs.split(/\s+/) if refs.respond_to?(:to_str)
465
+ self.git.rev_list({:timeout => false}, *refs).split("\n").map { |a| a[0, 40] }
466
+ end
467
+
468
+ def objects_between(ref1, ref2 = nil)
469
+ if ref2
470
+ refs = "#{ref2}..#{ref1}"
471
+ else
472
+ refs = ref1
473
+ end
474
+ self.objects(refs)
475
+ end
476
+
477
+ def diff_objects(commit_sha, parents = true)
478
+ revs = []
479
+ Gifts::Grit.no_quote = true
480
+ if parents
481
+ # PARENTS:
482
+ revs = self.git.diff_tree({:timeout => false, :r => true, :t => true, :m => true}, commit_sha).
483
+ strip.split("\n").map{ |a| r = a.split(' '); r[3] if r[1] != '160000' }
484
+ else
485
+ # NO PARENTS:
486
+ revs = self.git.native(:ls_tree, {:timeout => false, :r => true, :t => true}, commit_sha).
487
+ split("\n").map{ |a| a.split("\t").first.split(' ')[2] }
488
+ end
489
+ revs << self.commit(commit_sha).tree.id
490
+ Gifts::Grit.no_quote = false
491
+ return revs.uniq.compact
492
+ end
493
+
494
+ # The Tree object for the given treeish reference
495
+ # +treeish+ is the reference (default 'master')
496
+ # +paths+ is an optional Array of directory paths to restrict the tree (default [])
497
+ #
498
+ # Examples
499
+ # repo.tree('master', ['lib/'])
500
+ #
501
+ # Returns Gifts::Grit::Tree (baked)
502
+ def tree(treeish = 'master', paths = [])
503
+ Tree.construct(self, treeish, paths)
504
+ end
505
+
506
+ # quick way to get a simple array of hashes of the entries
507
+ # of a single tree or recursive tree listing from a given
508
+ # sha or reference
509
+ # +treeish+ is the reference (default 'master')
510
+ # +options+ is a hash or options - currently only takes :recursive
511
+ #
512
+ # Examples
513
+ # repo.lstree('master', :recursive => true)
514
+ #
515
+ # Returns array of hashes - one per tree entry
516
+ def lstree(treeish = 'master', options = {})
517
+ # check recursive option
518
+ opts = {:timeout => false, :l => true, :t => true}
519
+ if options[:recursive]
520
+ opts[:r] = true
521
+ end
522
+ # mode, type, sha, size, path
523
+ revs = self.git.native(:ls_tree, opts, treeish)
524
+ lines = revs.split("\n")
525
+ revs = lines.map do |a|
526
+ stuff, path = a.split("\t")
527
+ mode, type, sha, size = stuff.split(" ")
528
+ entry = {:mode => mode, :type => type, :sha => sha, :path => path}
529
+ entry[:size] = size.strip.to_i if size.strip != '-'
530
+ entry
531
+ end
532
+ revs
533
+ end
534
+
535
+ def object(sha)
536
+ obj = git.get_git_object(sha)
537
+ raw = Gifts::Grit::GitRuby::Internal::RawObject.new(obj[:type], obj[:content])
538
+ object = Gifts::Grit::GitRuby::GitObject.from_raw(raw)
539
+ object.sha = sha
540
+ object
541
+ end
542
+
543
+ # The Blob object for the given id
544
+ # +id+ is the SHA1 id of the blob
545
+ #
546
+ # Returns Gifts::Grit::Blob (unbaked)
547
+ def blob(id)
548
+ Blob.create(self, :id => id)
549
+ end
550
+
551
+ # The commit log for a treeish
552
+ #
553
+ # Returns Gifts::Grit::Commit[]
554
+ def log(commit = 'master', path = nil, options = {})
555
+ default_options = {:pretty => "raw"}
556
+ actual_options = default_options.merge(options)
557
+ arg = path ? [commit, '--', path] : [commit]
558
+ commits = self.git.log(actual_options, *arg)
559
+ Commit.list_from_string(self, commits)
560
+ end
561
+
562
+ # The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
563
+ # +a+ is the base commit
564
+ # +b+ is the other commit
565
+ # +paths+ is an optional list of file paths on which to restrict the diff
566
+ def diff(a, b, *paths)
567
+ diff = self.git.native('diff', {}, a, b, '--', *paths)
568
+
569
+ if diff =~ /diff --git a/
570
+ diff = diff.sub(/.*?(diff --git a)/m, '\1')
571
+ else
572
+ diff = ''
573
+ end
574
+ Diff.list_from_string(self, diff, a)
575
+ end
576
+
577
+ # The commit diff for the given commit
578
+ # +commit+ is the commit name/id
579
+ #
580
+ # Returns Gifts::Grit::Diff[]
581
+ def commit_diff(commit)
582
+ Commit.diff(self, commit)
583
+ end
584
+
585
+ # Archive the given treeish
586
+ # +treeish+ is the treeish name/id (default 'master')
587
+ # +prefix+ is the optional prefix
588
+ #
589
+ # Examples
590
+ # repo.archive_tar
591
+ # # => <String containing tar archive>
592
+ #
593
+ # repo.archive_tar('a87ff14')
594
+ # # => <String containing tar archive for commit a87ff14>
595
+ #
596
+ # repo.archive_tar('master', 'myproject/')
597
+ # # => <String containing tar archive and prefixed with 'myproject/'>
598
+ #
599
+ # Returns String (containing tar archive)
600
+ def archive_tar(treeish = 'master', prefix = nil)
601
+ options = {}
602
+ options[:prefix] = prefix if prefix
603
+ self.git.archive(options, treeish)
604
+ end
605
+
606
+ # Archive and gzip the given treeish
607
+ # +treeish+ is the treeish name/id (default 'master')
608
+ # +prefix+ is the optional prefix
609
+ #
610
+ # Examples
611
+ # repo.archive_tar_gz
612
+ # # => <String containing tar.gz archive>
613
+ #
614
+ # repo.archive_tar_gz('a87ff14')
615
+ # # => <String containing tar.gz archive for commit a87ff14>
616
+ #
617
+ # repo.archive_tar_gz('master', 'myproject/')
618
+ # # => <String containing tar.gz archive and prefixed with 'myproject/'>
619
+ #
620
+ # Returns String (containing tar.gz archive)
621
+ def archive_tar_gz(treeish = 'master', prefix = nil)
622
+ options = {}
623
+ options[:prefix] = prefix if prefix
624
+ self.git.archive(options, treeish, "| gzip -n")
625
+ end
626
+
627
+ # Write an archive directly to a file
628
+ # +treeish+ is the treeish name/id (default 'master')
629
+ # +prefix+ is the optional prefix (default nil)
630
+ # +filename+ is the name of the file (default 'archive.tar.gz')
631
+ # +format+ is the optional format (default nil)
632
+ # +pipe+ is the command to run the output through (default 'gzip')
633
+ #
634
+ # Returns nothing
635
+ def archive_to_file(treeish = 'master', prefix = nil, filename = 'archive.tar.gz', format = nil, pipe = "gzip")
636
+ options = {}
637
+ options[:prefix] = prefix if prefix
638
+ options[:format] = format if format
639
+ self.git.archive(options, treeish, "| #{pipe} > #{filename}")
640
+ end
641
+
642
+ # Enable git-daemon serving of this repository by writing the
643
+ # git-daemon-export-ok file to its git directory
644
+ #
645
+ # Returns nothing
646
+ def enable_daemon_serve
647
+ self.git.fs_write(DAEMON_EXPORT_FILE, '')
648
+ end
649
+
650
+ # Disable git-daemon serving of this repository by ensuring there is no
651
+ # git-daemon-export-ok file in its git directory
652
+ #
653
+ # Returns nothing
654
+ def disable_daemon_serve
655
+ self.git.fs_delete(DAEMON_EXPORT_FILE)
656
+ end
657
+
658
+ def gc_auto
659
+ self.git.gc({:auto => true})
660
+ end
661
+
662
+ # The list of alternates for this repo
663
+ #
664
+ # Returns Array[String] (pathnames of alternates)
665
+ def alternates
666
+ alternates_path = "objects/info/alternates"
667
+ self.git.fs_read(alternates_path).strip.split("\n")
668
+ rescue Errno::ENOENT
669
+ []
670
+ end
671
+
672
+ # Sets the alternates
673
+ # +alts+ is the Array of String paths representing the alternates
674
+ #
675
+ # Returns nothing
676
+ def alternates=(alts)
677
+ alts.each do |alt|
678
+ unless File.exist?(alt)
679
+ raise "Could not set alternates. Alternate path #{alt} must exist"
680
+ end
681
+ end
682
+
683
+ if alts.empty?
684
+ self.git.fs_write('objects/info/alternates', '')
685
+ else
686
+ self.git.fs_write('objects/info/alternates', alts.join("\n"))
687
+ end
688
+ end
689
+
690
+ def config
691
+ @config ||= Config.new(self)
692
+ end
693
+
694
+ def index
695
+ Index.new(self)
696
+ end
697
+
698
+ def update_ref(head, commit_sha)
699
+ return nil if !commit_sha || (commit_sha.size != 40)
700
+ self.git.fs_write("refs/heads/#{head}", commit_sha)
701
+ commit_sha
702
+ end
703
+
704
+ # Rename the current repository directory.
705
+ # +name+ is the new name
706
+ #
707
+ # Returns nothing
708
+ def rename(name)
709
+ if @bare
710
+ self.git.fs_move('/', "../#{name}")
711
+ else
712
+ self.git.fs_move('/', "../../#{name}")
713
+ end
714
+ end
715
+
716
+ # Pretty object inspection
717
+ def inspect
718
+ %Q{#<Gifts::Grit::Repo "#{@path}">}
719
+ end
720
+ end # Repo
721
+
722
+ end # Gifts::Grit