gifts 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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