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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +9 -0
- data/gifts.gemspec +32 -0
- data/lib/gifts.rb +22 -0
- data/lib/gifts/commit_table.rb +70 -0
- data/lib/gifts/database.rb +49 -0
- data/lib/gifts/diff_table.rb +55 -0
- data/lib/gifts/file_table.rb +51 -0
- data/lib/gifts/repo_table.rb +31 -0
- data/lib/gifts/table_base.rb +18 -0
- data/lib/gifts/term_table.rb +28 -0
- data/lib/gifts/user_table.rb +19 -0
- data/lib/gifts/version.rb +3 -0
- data/spec/.gitkeeper +0 -0
- data/vendor/lib/LICENSE-grit +22 -0
- data/vendor/lib/LICENSE-grit_ext +22 -0
- data/vendor/lib/gifts/grit.rb +73 -0
- data/vendor/lib/gifts/grit/actor.rb +52 -0
- data/vendor/lib/gifts/grit/blame.rb +70 -0
- data/vendor/lib/gifts/grit/blob.rb +126 -0
- data/vendor/lib/gifts/grit/commit.rb +324 -0
- data/vendor/lib/gifts/grit/commit_stats.rb +128 -0
- data/vendor/lib/gifts/grit/config.rb +44 -0
- data/vendor/lib/gifts/grit/diff.rb +97 -0
- data/vendor/lib/gifts/grit/errors.rb +10 -0
- data/vendor/lib/gifts/grit/git-ruby.rb +262 -0
- data/vendor/lib/gifts/grit/git-ruby/commit_db.rb +52 -0
- data/vendor/lib/gifts/grit/git-ruby/git_object.rb +353 -0
- data/vendor/lib/gifts/grit/git-ruby/internal/file_window.rb +58 -0
- data/vendor/lib/gifts/grit/git-ruby/internal/loose.rb +137 -0
- data/vendor/lib/gifts/grit/git-ruby/internal/pack.rb +398 -0
- data/vendor/lib/gifts/grit/git-ruby/internal/raw_object.rb +44 -0
- data/vendor/lib/gifts/grit/git-ruby/repository.rb +784 -0
- data/vendor/lib/gifts/grit/git.rb +501 -0
- data/vendor/lib/gifts/grit/index.rb +222 -0
- data/vendor/lib/gifts/grit/lazy.rb +35 -0
- data/vendor/lib/gifts/grit/merge.rb +45 -0
- data/vendor/lib/gifts/grit/ref.rb +98 -0
- data/vendor/lib/gifts/grit/repo.rb +722 -0
- data/vendor/lib/gifts/grit/ruby1.9.rb +15 -0
- data/vendor/lib/gifts/grit/status.rb +153 -0
- data/vendor/lib/gifts/grit/submodule.rb +88 -0
- data/vendor/lib/gifts/grit/tag.rb +97 -0
- data/vendor/lib/gifts/grit/tree.rb +125 -0
- data/vendor/lib/gifts/grit_ext.rb +41 -0
- data/vendor/lib/gifts/grit_ext/actor.rb +15 -0
- data/vendor/lib/gifts/grit_ext/blob.rb +26 -0
- data/vendor/lib/gifts/grit_ext/commit.rb +15 -0
- data/vendor/lib/gifts/grit_ext/diff.rb +23 -0
- data/vendor/lib/gifts/grit_ext/tag.rb +10 -0
- data/vendor/lib/gifts/grit_ext/tree.rb +10 -0
- data/vendor/lib/gifts/grit_ext/version.rb +7 -0
- metadata +256 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
class String
|
2
|
+
if self.method_defined?(:ord)
|
3
|
+
def getord(offset); self[offset].ord; end
|
4
|
+
else
|
5
|
+
alias :getord :[]
|
6
|
+
end
|
7
|
+
|
8
|
+
unless self.method_defined?(:b)
|
9
|
+
if self.method_defined?(:force_encoding)
|
10
|
+
def b; self.dup.force_encoding(Encoding::ASCII_8BIT); end
|
11
|
+
else
|
12
|
+
def b; self.dup; end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module Gifts::Grit
|
2
|
+
|
3
|
+
class Status
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :files
|
7
|
+
|
8
|
+
@base = nil
|
9
|
+
@files = nil
|
10
|
+
|
11
|
+
def initialize(base)
|
12
|
+
@base = base
|
13
|
+
construct_status
|
14
|
+
end
|
15
|
+
|
16
|
+
def changed
|
17
|
+
@files.select { |k, f| f.type == 'M' }
|
18
|
+
end
|
19
|
+
|
20
|
+
def added
|
21
|
+
@files.select { |k, f| f.type == 'A' }
|
22
|
+
end
|
23
|
+
|
24
|
+
def deleted
|
25
|
+
@files.select { |k, f| f.type == 'D' }
|
26
|
+
end
|
27
|
+
|
28
|
+
def untracked
|
29
|
+
@files.select { |k, f| f.untracked }
|
30
|
+
end
|
31
|
+
|
32
|
+
def pretty
|
33
|
+
out = ''
|
34
|
+
self.each do |file|
|
35
|
+
out << file.path
|
36
|
+
out << "\n\tsha(r) " + file.sha_repo.to_s + ' ' + file.mode_repo.to_s
|
37
|
+
out << "\n\tsha(i) " + file.sha_index.to_s + ' ' + file.mode_index.to_s
|
38
|
+
out << "\n\ttype " + file.type.to_s
|
39
|
+
out << "\n\tstage " + file.stage.to_s
|
40
|
+
out << "\n\tuntrac " + file.untracked.to_s
|
41
|
+
out << "\n"
|
42
|
+
end
|
43
|
+
out << "\n"
|
44
|
+
out
|
45
|
+
end
|
46
|
+
|
47
|
+
# enumerable method
|
48
|
+
|
49
|
+
def [](file)
|
50
|
+
@files[file]
|
51
|
+
end
|
52
|
+
|
53
|
+
def each
|
54
|
+
@files.each do |k, file|
|
55
|
+
yield file
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class StatusFile
|
60
|
+
attr_accessor :path, :type, :stage, :untracked
|
61
|
+
attr_accessor :mode_index, :mode_repo
|
62
|
+
attr_accessor :sha_index, :sha_repo
|
63
|
+
|
64
|
+
@base = nil
|
65
|
+
|
66
|
+
def initialize(base, hash)
|
67
|
+
@base = base
|
68
|
+
@path = hash[:path]
|
69
|
+
@type = hash[:type]
|
70
|
+
@stage = hash[:stage]
|
71
|
+
@mode_index = hash[:mode_index]
|
72
|
+
@mode_repo = hash[:mode_repo]
|
73
|
+
@sha_index = hash[:sha_index]
|
74
|
+
@sha_repo = hash[:sha_repo]
|
75
|
+
@untracked = hash[:untracked]
|
76
|
+
end
|
77
|
+
|
78
|
+
def blob(type = :index)
|
79
|
+
if type == :repo
|
80
|
+
@base.object(@sha_repo)
|
81
|
+
else
|
82
|
+
@base.object(@sha_index) rescue @base.object(@sha_repo)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def construct_status
|
91
|
+
@files = ls_files
|
92
|
+
|
93
|
+
Dir.chdir(@base.working_dir) do
|
94
|
+
# find untracked in working dir
|
95
|
+
Dir.glob('**/*') do |file|
|
96
|
+
if !@files[file]
|
97
|
+
@files[file] = {:path => file, :untracked => true} if !File.directory?(file)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# find modified in tree
|
102
|
+
diff_files.each do |path, data|
|
103
|
+
@files[path] ? @files[path].merge!(data) : @files[path] = data
|
104
|
+
end
|
105
|
+
|
106
|
+
# find added but not committed - new files
|
107
|
+
diff_index('HEAD').each do |path, data|
|
108
|
+
@files[path] ? @files[path].merge!(data) : @files[path] = data
|
109
|
+
end
|
110
|
+
|
111
|
+
@files.each do |k, file_hash|
|
112
|
+
@files[k] = StatusFile.new(@base, file_hash)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# compares the index and the working directory
|
118
|
+
def diff_files
|
119
|
+
hsh = {}
|
120
|
+
@base.git.diff_files.split("\n").each do |line|
|
121
|
+
(info, file) = line.split("\t")
|
122
|
+
(mode_src, mode_dest, sha_src, sha_dest, type) = info.split
|
123
|
+
hsh[file] = {:path => file, :mode_file => mode_src.to_s[1, 7], :mode_index => mode_dest,
|
124
|
+
:sha_file => sha_src, :sha_index => sha_dest, :type => type}
|
125
|
+
end
|
126
|
+
hsh
|
127
|
+
end
|
128
|
+
|
129
|
+
# compares the index and the repository
|
130
|
+
def diff_index(treeish)
|
131
|
+
hsh = {}
|
132
|
+
@base.git.diff_index({}, treeish).split("\n").each do |line|
|
133
|
+
(info, file) = line.split("\t")
|
134
|
+
(mode_src, mode_dest, sha_src, sha_dest, type) = info.split
|
135
|
+
hsh[file] = {:path => file, :mode_repo => mode_src.to_s[1, 7], :mode_index => mode_dest,
|
136
|
+
:sha_repo => sha_src, :sha_index => sha_dest, :type => type}
|
137
|
+
end
|
138
|
+
hsh
|
139
|
+
end
|
140
|
+
|
141
|
+
def ls_files
|
142
|
+
hsh = {}
|
143
|
+
lines = @base.git.ls_files({:stage => true})
|
144
|
+
lines.split("\n").each do |line|
|
145
|
+
(info, file) = line.split("\t")
|
146
|
+
(mode, sha, stage) = info.split
|
147
|
+
hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
|
148
|
+
end
|
149
|
+
hsh
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Gifts::Grit
|
2
|
+
|
3
|
+
class Submodule
|
4
|
+
attr_reader :id
|
5
|
+
attr_reader :mode
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
# Create a Submodule containing just the specified attributes
|
9
|
+
# +repo+ is the Repo
|
10
|
+
# +atts+ is a Hash of instance variable data
|
11
|
+
#
|
12
|
+
# Returns Gifts::Grit::Submodule (unbaked)
|
13
|
+
def self.create(repo, atts)
|
14
|
+
self.allocate.create_initialize(repo, atts)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Initializer for Submodule.create
|
18
|
+
# +repo+ is the Repo
|
19
|
+
# +atts+ is a Hash of instance variable data
|
20
|
+
#
|
21
|
+
# Returns Gifts::Grit::Submodule
|
22
|
+
def create_initialize(repo, atts)
|
23
|
+
@repo = repo
|
24
|
+
atts.each do |k, v|
|
25
|
+
instance_variable_set("@#{k}".to_sym, v)
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# The url of this submodule
|
31
|
+
# +ref+ is the committish that should be used to look up the url
|
32
|
+
#
|
33
|
+
# Returns String
|
34
|
+
def url(ref)
|
35
|
+
config = self.class.config(@repo, ref)
|
36
|
+
|
37
|
+
lookup = config.keys.inject({}) do |acc, key|
|
38
|
+
id = config[key]['id']
|
39
|
+
acc[id] = config[key]['url']
|
40
|
+
acc
|
41
|
+
end
|
42
|
+
|
43
|
+
lookup[@id]
|
44
|
+
end
|
45
|
+
|
46
|
+
# The configuration information for the given +repo+
|
47
|
+
# +repo+ is the Repo
|
48
|
+
# +ref+ is the committish (defaults to 'master')
|
49
|
+
#
|
50
|
+
# Returns a Hash of { <path:String> => { 'url' => <url:String>, 'id' => <id:String> } }
|
51
|
+
# Returns {} if no .gitmodules file was found
|
52
|
+
def self.config(repo, ref = "master")
|
53
|
+
commit = repo.commit(ref)
|
54
|
+
blob = commit.tree/'.gitmodules'
|
55
|
+
return {} unless blob
|
56
|
+
|
57
|
+
lines = blob.data.gsub(/\r\n?/, "\n" ).split("\n")
|
58
|
+
|
59
|
+
config = {}
|
60
|
+
current = nil
|
61
|
+
|
62
|
+
lines.each do |line|
|
63
|
+
if line =~ /^\[submodule "(.+)"\]$/
|
64
|
+
current = $1
|
65
|
+
config[current] = {}
|
66
|
+
config[current]['id'] = (commit.tree/current).id
|
67
|
+
elsif line =~ /^\t(\w+) = (.+)$/
|
68
|
+
config[current][$1] = $2
|
69
|
+
config[current]['id'] = (commit.tree/$2).id if $1 == 'path'
|
70
|
+
else
|
71
|
+
# ignore
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
config
|
76
|
+
end
|
77
|
+
|
78
|
+
def basename
|
79
|
+
File.basename(name)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Pretty object inspection
|
83
|
+
def inspect
|
84
|
+
%Q{#<Gifts::Grit::Submodule "#{@id}">}
|
85
|
+
end
|
86
|
+
end # Submodule
|
87
|
+
|
88
|
+
end # Gifts::Grit
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Gifts::Grit
|
2
|
+
|
3
|
+
class Tag < Ref
|
4
|
+
extend Lazy
|
5
|
+
|
6
|
+
lazy_reader :message
|
7
|
+
lazy_reader :tagger
|
8
|
+
lazy_reader :tag_date
|
9
|
+
|
10
|
+
# Writes a new tag object from a hash
|
11
|
+
# +repo+ is a Gifts::Grit repo
|
12
|
+
# +hash+ is the hash of tag values
|
13
|
+
#
|
14
|
+
# Returns a hash with +sha+ and +size+ of the created object
|
15
|
+
def self.create_tag_object(repo, hash, default_actor = nil)
|
16
|
+
tagger = hash[:tagger]
|
17
|
+
if !tagger
|
18
|
+
tagger = default_actor ? default_actor : Actor.new("none", "none@none")
|
19
|
+
tagger_date = Time.now
|
20
|
+
else
|
21
|
+
tagger_date = tagger[:date] ? Time.parse(tagger[:date]) : Time.now
|
22
|
+
tagger = Actor.new(tagger[:name], tagger[:email])
|
23
|
+
end
|
24
|
+
data = []
|
25
|
+
data << "object #{hash[:object]}"
|
26
|
+
data << "type #{hash[:type]}"
|
27
|
+
data << "tag #{hash[:tag]}"
|
28
|
+
data << "tagger #{tagger.output(tagger_date)}"
|
29
|
+
data << ""
|
30
|
+
data << hash[:message]
|
31
|
+
data = data.join("\n")
|
32
|
+
sha = repo.git.put_raw_object(data, 'tag')
|
33
|
+
{ :sha => sha, :size => data.size }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Parses the results from `cat-file -p`
|
37
|
+
#
|
38
|
+
# data - String tag object data. Example:
|
39
|
+
# object 7bcc0ee821cdd133d8a53e8e7173a334fef448aa
|
40
|
+
# type commit
|
41
|
+
# tag v0.7.0
|
42
|
+
# tagger USER <EMAIL> DATE
|
43
|
+
#
|
44
|
+
# v0.7.0
|
45
|
+
#
|
46
|
+
# Returns parsed Hash. Example:
|
47
|
+
# {:message => "...", :tagger => "bob", :tag_date => ...}
|
48
|
+
def self.parse_tag_data(data)
|
49
|
+
return unless data =~ /^object/
|
50
|
+
parsed = {}
|
51
|
+
lines = data.split("\n")
|
52
|
+
parsed[:object] = lines.shift.sub(/^object /, '')
|
53
|
+
parsed[:type] = lines.shift.sub(/^type /, '')
|
54
|
+
parsed[:tag] = lines.shift.sub(/^tag /, '')
|
55
|
+
author_line = lines.shift
|
56
|
+
parsed[:tagger], parsed[:tag_date] = Commit.actor(author_line)
|
57
|
+
if !parsed[:tagger] || !parsed[:tagger].name
|
58
|
+
parsed[:tag_date] ||= Time.utc(1970)
|
59
|
+
parsed[:tagger] = Actor.from_string(author_line.sub(/^tagger /, ''))
|
60
|
+
end
|
61
|
+
lines.shift # blank line
|
62
|
+
parsed[:message] = []
|
63
|
+
while lines.first && lines.first !~ /-----BEGIN PGP SIGNATURE-----/
|
64
|
+
parsed[:message] << lines.shift
|
65
|
+
end
|
66
|
+
parsed[:message] = parsed[:message] * "\n"
|
67
|
+
parsed[:pgp] = []
|
68
|
+
while lines.first
|
69
|
+
parsed[:pgp] << lines.shift
|
70
|
+
end
|
71
|
+
parsed[:pgp] = parsed[:pgp] * "\n"
|
72
|
+
parsed
|
73
|
+
end
|
74
|
+
|
75
|
+
def lazy_source
|
76
|
+
data = commit.repo.git.cat_ref({:p => true}, name)
|
77
|
+
@message = commit.short_message
|
78
|
+
@tagger = commit.author
|
79
|
+
@tag_date = commit.authored_date
|
80
|
+
return self if data.empty?
|
81
|
+
|
82
|
+
if parsed = self.class.parse_tag_data(data)
|
83
|
+
@message = parsed[:message]
|
84
|
+
@tagger = parsed[:tagger]
|
85
|
+
@tag_date = parsed[:tag_date]
|
86
|
+
end
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_commit
|
91
|
+
sha = @repo_ref.git.commit_from_sha(@commit_id)
|
92
|
+
raise "Unknown object type." if sha == ''
|
93
|
+
Commit.create(@repo_ref, :id => sha)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Gifts::Grit
|
2
|
+
|
3
|
+
class Tree
|
4
|
+
extend Lazy
|
5
|
+
|
6
|
+
lazy_reader :contents
|
7
|
+
attr_reader :id
|
8
|
+
attr_reader :mode
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
# Construct the contents of the tree
|
12
|
+
# +repo+ is the Repo
|
13
|
+
# +treeish+ is the reference
|
14
|
+
# +paths+ is an optional Array of directory paths to restrict the tree
|
15
|
+
#
|
16
|
+
# Returns Gifts::Grit::Tree (baked)
|
17
|
+
def self.construct(repo, treeish, paths = [])
|
18
|
+
output = repo.git.ls_tree({:raise => true}, treeish, *paths)
|
19
|
+
self.allocate.construct_initialize(repo, treeish, output)
|
20
|
+
end
|
21
|
+
|
22
|
+
def construct_initialize(repo, id, text)
|
23
|
+
@repo = repo
|
24
|
+
@id = id
|
25
|
+
@contents = []
|
26
|
+
|
27
|
+
text.split("\n").each do |line|
|
28
|
+
@contents << content_from_string(repo, line)
|
29
|
+
end
|
30
|
+
@contents.compact!
|
31
|
+
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def lazy_source
|
36
|
+
Tree.construct(@repo, @id, [])
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create an unbaked Tree containing just the specified attributes
|
40
|
+
# +repo+ is the Repo
|
41
|
+
# +atts+ is a Hash of instance variable data
|
42
|
+
#
|
43
|
+
# Returns Gifts::Grit::Tree (unbaked)
|
44
|
+
def self.create(repo, atts)
|
45
|
+
self.allocate.create_initialize(repo, atts)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Initializer for Tree.create
|
49
|
+
# +repo+ is the Repo
|
50
|
+
# +atts+ is a Hash of instance variable data
|
51
|
+
#
|
52
|
+
# Returns Gifts::Grit::Tree (unbaked)
|
53
|
+
def create_initialize(repo, atts)
|
54
|
+
@repo = repo
|
55
|
+
|
56
|
+
atts.each do |k, v|
|
57
|
+
instance_variable_set("@#{k}", v)
|
58
|
+
end
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Parse a content item and create the appropriate object
|
63
|
+
# +repo+ is the Repo
|
64
|
+
# +text+ is the single line containing the items data in `git ls-tree` format
|
65
|
+
#
|
66
|
+
# Returns Gifts::Grit::Blob or Gifts::Grit::Tree
|
67
|
+
def content_from_string(repo, text)
|
68
|
+
mode, type, id, name = text.split(/ |\t/, 4)
|
69
|
+
case type
|
70
|
+
when "tree"
|
71
|
+
Tree.create(repo, :id => id, :mode => mode, :name => name)
|
72
|
+
when "blob"
|
73
|
+
Blob.create(repo, :id => id, :mode => mode, :name => name)
|
74
|
+
when "link"
|
75
|
+
Blob.create(repo, :id => id, :mode => mode, :name => name)
|
76
|
+
when "commit"
|
77
|
+
Submodule.create(repo, :id => id, :mode => mode, :name => name)
|
78
|
+
else
|
79
|
+
raise Gifts::Grit::InvalidObjectType, type
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Find the named object in this tree's contents
|
84
|
+
#
|
85
|
+
# Examples
|
86
|
+
# Repo.new('/path/to/grit').tree/'lib'
|
87
|
+
# # => #<Gifts::Grit::Tree "6cc23ee138be09ff8c28b07162720018b244e95e">
|
88
|
+
# Repo.new('/path/to/grit').tree/'README.txt'
|
89
|
+
# # => #<Gifts::Grit::Blob "8b1e02c0fb554eed2ce2ef737a68bb369d7527df">
|
90
|
+
#
|
91
|
+
# Returns Gifts::Grit::Blob or Gifts::Grit::Tree or nil if not found
|
92
|
+
def /(file)
|
93
|
+
if file =~ /\//
|
94
|
+
file.split("/").inject(self) { |acc, x| acc/x } rescue nil
|
95
|
+
else
|
96
|
+
self.contents.find { |c| c.name == file }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def basename
|
101
|
+
File.basename(name)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Pretty object inspection
|
105
|
+
def inspect
|
106
|
+
%Q{#<Gifts::Grit::Tree "#{@id}">}
|
107
|
+
end
|
108
|
+
|
109
|
+
# Find only Tree objects from contents
|
110
|
+
def trees
|
111
|
+
contents.select {|v| v.kind_of? Tree}
|
112
|
+
end
|
113
|
+
|
114
|
+
# Find only Blob objects from contents
|
115
|
+
def blobs
|
116
|
+
contents.select {|v| v.kind_of? Blob}
|
117
|
+
end
|
118
|
+
|
119
|
+
# Compares trees by name
|
120
|
+
def <=>(other)
|
121
|
+
name <=> other.name
|
122
|
+
end
|
123
|
+
end # Tree
|
124
|
+
|
125
|
+
end # Gifts::Grit
|