Class: Gitgo::Git::Tree

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/gitgo/git/tree.rb

Overview

Tree represents an in-memory working tree for git. Trees are initialized with a Grit::Tree. In general tree contents are represented as (path, [:mode,sha]) pairs, but subtrees can be expanded into (path, Tree) pairs.

See Git for an example of Tree usage in practice.

Efficiency

Modes are symbolized in the internal [:mode,sha] entries because they are rarely needed as strings and are typically very redundant. Symbolizing shas makes less sense because they are frequently used as strings. However it does make sense to use the same string instance to represent a sha in multiple places. As a result trees have an internal string_table that functions like a symbol table, ie it maps the same string content to a single shared instance. The string table is managed at the class level through the string_table method.

Trees only expand as needed. This saves memory and cycles because it is expensive to read, parse, and maintain the git tree data. In general trees will stay fairly compact unless certain expensive operations are performed. These are:

  • each_pair (with expand == true)

  • each_tree (with expand == true)

  • flatten

  • to_hash (with expand == true)

Avoid these methods if possible, or ensure they are rarely executed.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tree = nil) ⇒ Tree

Initializes a new Tree. The input tree should be a Grit::Tree or nil.



49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/gitgo/git/tree.rb', line 49

def initialize(tree=nil)
  @index = nil
  @tree = tree
  
  if tree
    self.mode = tree.mode
    self.sha  = tree.id
  else
    @mode = nil
    @sha = nil
  end
end

Instance Attribute Details

#modeObject

The tree mode.



46
47
48
# File 'lib/gitgo/git/tree.rb', line 46

def mode
  @mode
end

Class Method Details

.string_table(clear = false) ⇒ Object

Returns the string table for shas. Specify clear to reset the string table.



38
39
40
41
# File 'lib/gitgo/git/tree.rb', line 38

def string_table(clear=false)
  @string_table.clear if clear
  @string_table ||= Hash.new {|hash, key| hash[key] = key.freeze }
end

Instance Method Details

#==(another) ⇒ Object

Returns true if the to_hash results of self and another are equal.



239
240
241
# File 'lib/gitgo/git/tree.rb', line 239

def ==(another)
  self.to_hash == another.to_hash
end

#[](path) ⇒ Object

Returns the entry for the specified path, either a [:mode,sha] pair for a blob or a Tree for a subtree.



102
103
104
105
106
107
108
109
110
111
# File 'lib/gitgo/git/tree.rb', line 102

def [](path)
  case
  when entry = index[path]
    entry
  when tree = index.delete(path.to_sym)
    index[string(path)] = Tree.new(tree)
  else
    nil
  end
end

#[]=(path, entry) ⇒ Object

Sets the entry for the specified path. The entry should be a

:mode,sha

array, or a Tree. A nil entry indicates removal.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/gitgo/git/tree.rb', line 115

def []=(path, entry)
  # ensure an unexpanded tree is removed
  index.delete(path.to_sym)
  
  path = string(path)
  case entry
  when Array
    mode, sha = entry
    index[path] = [mode.to_sym, string(sha)]
  when Tree
    index[path] = entry
  when nil
    index.delete(path)
  else
    raise "invalid entry: #{entry.inspect}"
  end
  
  # add/remove content modifies self so
  # the sha can and should be invalidated
  @sha = nil
end

#eachObject

Yields each (path, entry) pair to the block, as an array, ordered by path. Implemented to get access to the enumerable methods.



146
147
148
149
150
151
# File 'lib/gitgo/git/tree.rb', line 146

def each
  each_pair do |key, value|
    yield [key, value]
  end
  self
end

#each_blobObject

Yields the (path, [:mode, sha]) pairs for each blob to the block.



168
169
170
171
172
173
# File 'lib/gitgo/git/tree.rb', line 168

def each_blob
  each_pair do |key, value|
    next unless value.kind_of?(Array)
    yield(key, value)
  end
end

#each_pair(expand = false) ⇒ Object

Yields each (path, entry) pair to the block, ordered by path. Entries can be [:mode,sha] arrays or Trees. If expand is true then subtrees will be expanded, but strongly consider whether or not expansion is necessary because it is computationally expensive.



157
158
159
160
161
162
163
164
165
# File 'lib/gitgo/git/tree.rb', line 157

def each_pair(expand=false)
  
  # sorting the keys is important when writing the tree;
  # unsorted keys cause warnings in git fsck
  keys = index.keys.sort_by {|key| key.to_s }
  store = expand ? self : index
  
  keys.each {|key| yield(key, store[key]) }
end

#each_tree(expand = false) ⇒ Object

Yields the (path, entry) pairs for each tree to the block. Subtrees are expanded if specified, in which case all entries will be Trees. Without expansion, entries may be [:mode,sha] arrays or Trees.



178
179
180
181
182
183
# File 'lib/gitgo/git/tree.rb', line 178

def each_tree(expand=false)
  each_pair(expand) do |key, value|
    next unless value.kind_of?(Tree)
    yield(key, value)
  end
end

#eql?(another) ⇒ Boolean

Returns true if the to_hash results of self and another are equal.

Returns:

  • (Boolean)


234
235
236
# File 'lib/gitgo/git/tree.rb', line 234

def eql?(another)
  self.to_hash == another.to_hash
end

#flatten(prefix = nil, target = {}) ⇒ Object

Flattens all paths under self into a single array.



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/gitgo/git/tree.rb', line 203

def flatten(prefix=nil, target={})
  keys.each do |key|
    next unless entry = self[key]

    key = key.to_s
    key = File.join(prefix, key) if prefix

    if entry.kind_of?(Tree)
      entry.flatten(key, target)
    else
      target[key] = entry
    end
  end

  target
end

#keysObject

Returns the keys (ie paths) for all entries in self. Keys are returned as strings



96
97
98
# File 'lib/gitgo/git/tree.rb', line 96

def keys
  index.keys.collect {|keys| keys.to_s }
end

#merge!(another) ⇒ Object



137
138
139
140
141
142
# File 'lib/gitgo/git/tree.rb', line 137

def merge!(another)
  another.each_pair do |path, entry|
    self[path] = entry
  end
  self
end

#sha(check = true) ⇒ Object

Returns the sha representing the contents for self. If check is true, sha will check that neither self nor any subtree is modified before returning the sha. If modified, the sha is set to nil to flag a repo to recalculate the sha on commit.

Note that check does not validate the sha correctly represents the contents of self.



81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/gitgo/git/tree.rb', line 81

def sha(check=true)
  if @sha && check
    index.each_value do |value|
      if value.kind_of?(Tree) && value.sha.nil?
        @sha = nil
        break
      end
    end
  end

  @sha
end

#sha=(sha) ⇒ Object

Sets the sha for self. Sha may be set to nil, in which case it will be calculated when a repo is committed.



70
71
72
# File 'lib/gitgo/git/tree.rb', line 70

def sha=(sha)
  @sha = sha ? string(sha) : nil
end

#subtree(segments, force = false) ⇒ Object

Returns the subtree indicated by the specified segments (an array of paths), or nil if no such subtree exists. If force is true then missing subtrees will be created.



188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/gitgo/git/tree.rb', line 188

def subtree(segments, force=false)
  return self if segments.empty?
  
  key = segments.shift
  tree = self[key]

  if !tree.kind_of?(Tree)
    return nil unless force
    self[key] = tree = Tree.new(nil)
  end
  
  tree.subtree(segments, force)
end

#to_hash(expand = false) ⇒ Object

Returns self as a hash, expanding if specified.



221
222
223
224
225
226
227
228
229
230
231
# File 'lib/gitgo/git/tree.rb', line 221

def to_hash(expand=false)
  hash = {}
  each_pair(expand) do |key, value|
    hash[key] = case value
    when Tree  then value.to_hash
    when Array then value
    else to_entry(value)
    end
  end
  hash
end

#write_to(git) ⇒ Object

Writes self to the git instance. All subtrees will likewise be written. Returns a [mode, sha] entry.

Tree format:

mode name\0[packedsha]mode name\0[packedsha]...

Note there are no newlines separating tree entries.



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/gitgo/git/tree.rb', line 251

def write_to(git)
  self.mode ||= git.default_tree_mode
  self.sha  ||= begin
    lines = []
    each_pair(false) do |key, entry|
      mode, sha = case entry
      when Tree  then entry.write_to(git)
      when Array then entry
      else [entry.mode, entry.id]
      end
      
      # modes should not begin with zeros (although it is not fatal
      # if they do), otherwise fsck will print warnings like this:
      #
      # warning in tree 980127...: contains zero-padded file modes
      lines << zero_strip("#{mode} #{key}\0#{[sha].pack("H*")}")
    end

    git.set(:tree, lines.join)
  end

  [mode, sha]
end