Class: Silo::Repository

Inherits:
Object
  • Object
show all
Defined in:
lib/silo/repository.rb

Overview

Represents a Silo repository

This provides the core features of Silo to initialize a repository and work with it.

Author:

  • Sebastian Staudt

Since:

  • 0.1.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, options = {}) ⇒ Repository

Creates a new repository instance on the given path

Parameters:

  • options (Hash) (defaults to: {})

    A hash of options

Options Hash (options):

  • :create (Boolean) — default: true

    Creates the backing Git repository if it does not already exist

  • :prepare (Boolean) — default: true

    Prepares the backing Git repository for use with Silo if not already done

Raises:

  • (Grit::InvalidGitRepositoryError)

    if the path exists, but is not a valid Git repository

  • (Grit::NoSuchPathError)

    if the path does not exist and option :create is +false+

  • (InvalidRepositoryError)

    if the path contains another Git repository that does not contain data managed by Silo.

Since:

  • 0.1.0



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/silo/repository.rb', line 47

def initialize(path, options = {})
  options = {
    :create  => true,
    :prepare => true
  }.merge options

  @path = File.expand_path path

  if File.exist?(@path) && Dir.entries(@path).size > 2
    unless File.exist?(File.join(@path, 'HEAD')) &&
           File.stat(File.join(@path, 'objects')).directory? &&
           File.stat(File.join(@path, 'refs')).directory?
      raise Grit::InvalidGitRepositoryError.new(@path)
    end
    @git = Grit::Repo.new(@path, { :is_bare => true })
  else
    if options[:create]
      @git = Grit::Repo.init_bare(@path, {}, { :is_bare => true })
    else
      raise Grit::NoSuchPathError.new(@path)
    end
  end

  if !prepared? && @git.commit_count > 0
    raise InvalidRepositoryError.new(@path)
  end

  @remotes = {}
  @remotes.merge! git_remotes

  prepare if options[:prepare] && !prepared?
end

Instance Attribute Details

#gitGrit::Repo (readonly)

Returns The Grit object to access the Git repository.

Returns:

  • (Grit::Repo)

    The Grit object to access the Git repository

Since:

  • 0.1.0



24
25
26
# File 'lib/silo/repository.rb', line 24

def git
  @git
end

#pathString (readonly)

Returns The file system path of the repository.

Returns:

  • (String)

    The file system path of the repository

Since:

  • 0.1.0



27
28
29
# File 'lib/silo/repository.rb', line 27

def path
  @path
end

#remotesHash<Remote::Base> (readonly)

Returns The remote repositories configured for this repository.

Returns:

  • (Hash<Remote::Base>)

    The remote repositories configured for this repository

Since:

  • 0.1.0



31
32
33
# File 'lib/silo/repository.rb', line 31

def remotes
  @remotes
end

Instance Method Details

#add(path, prefix = nil) ⇒ Object

Stores a file or full directory structure into the repository inside an optional prefix path

This adds one commit to the history of the repository including the file or directory structure. If the file or directory already existed inside the prefix, Git will only save the changes.

Parameters:

  • path (String)

    The path of the file or directory to store into the repository

  • prefix (String) (defaults to: nil)

    An optional prefix where the file is stored inside the repository

Since:

  • 0.1.0



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/silo/repository.rb', line 91

def add(path, prefix = nil)
  path   = File.expand_path path
  prefix ||= '/'
  in_work_tree File.dirname(path) do
    index = @git.index
    index.read_tree 'HEAD'
    add = lambda do |f, p|
      file = File.basename f
      pre  = (p == '/') ? file : File.join(p, file)
      dir  = File.stat(f).directory?
      if dir
        (Dir.entries(f) - %w{. ..}).each do |child|
          add.call File.join(f, child), pre
        end
      else
        index.add pre, IO.read(f)
      end
      dir
    end
    dir = add.call path, prefix
    type = dir ? 'directory' : 'file'
    commit_msg = "Added #{type} #{path} into '#{prefix}'"
    index.commit commit_msg, [@git.head.commit.sha]
  end
end

#add_remote(name, url) ⇒ Object

Adds a new remote to this Repository

Parameters:

  • name (String)

    The name of the remote to add

  • url (String)

    The URL of the remote repository

See Also:

Since:

  • 0.1.0



122
123
124
125
# File 'lib/silo/repository.rb', line 122

def add_remote(name, url)
  @remotes[name] = Remote::Git.new(self, name, url)
  @remotes[name].add
end

#contents(path = nil) ⇒ Array<String>

Gets a list of files and directories in the specified path inside the repository

Parameters:

  • path (String) (defaults to: nil)

    The path to search for inside the repository

Returns:

  • (Array<String>)

    All files and directories found in the specidied path

Since:

  • 0.1.0



133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/silo/repository.rb', line 133

def contents(path = nil)
  contents = []

  object = find_object(path || '/')
  contents << path unless path.nil? || object.nil?
  if object.is_a? Grit::Tree
    (object.blobs + object.trees).each do |obj|
      contents += contents(path.nil? ? obj.basename : File.join(path, obj.basename))
    end
  end

  contents
end

#distributeObject

Push the current state of the repository to each attached remote repository

See Also:

Since:

  • 0.1.0



151
152
153
# File 'lib/silo/repository.rb', line 151

def distribute
  @remotes.each_value { |remote| remote.push }
end

#find_object(path = '/') ⇒ Grit::Blob, Grit::Tree

Returns the object (tree or blob) at the given path inside the repository

Parameters:

  • path (String) (defaults to: '/')

    The path of the object in the repository

Returns:

  • (Grit::Blob, Grit::Tree)

    The object at the given path

Raises:

Since:

  • 0.1.0



160
161
162
# File 'lib/silo/repository.rb', line 160

def find_object(path = '/')
  (path == '/') ? @git.tree : @git.tree/path
end

#git_remotesObject

Loads remotes from the backing Git repository's configuration

See Also:

Since:

  • 0.1.0



167
168
169
170
171
172
173
174
# File 'lib/silo/repository.rb', line 167

def git_remotes
  remotes = {}
  @git.git.remote.split.each do |remote|
    url = @git.git.config({}, '--get', "remote.#{remote}.url").strip
    remotes[remote] = Remote::Git.new(self, remote, url)
  end
  remotes
end

#history(path = nil) ⇒ Array<Grit::Commit>

Generate a history of Git commits for either the complete repository or a specified file or directory

Parameters:

  • path (String) (defaults to: nil)

    The path of the file or directory to generate the history for. If +nil+, the history of the entire repository will be returned.

Returns:

  • (Array<Grit::Commit>)

    The commit history for the repository or given path

Since:

  • 0.1.0



184
185
186
187
188
189
# File 'lib/silo/repository.rb', line 184

def history(path = nil)
  params = ['--format=raw']
  params += ['--', path] unless path.nil?
  output = @git.git.log({}, *params)
  Grit::Commit.list_from_string @git, output
end

#in_work_tree(path) {|path| ... } ⇒ Object

Run a block of code with +$GIT_WORK_TREE+ set to a specified path

This executes a block of code while the environment variable +$GIT_WORK_TREE+ is set to a specified path or alternatively the path of a temporary directory.

Parameters:

  • path (String, :tmp)

    A path or +:tmp+ which will create a temporary directory that will be removed afterwards

Yields:

  • (path)

    The code inside this block will be executed with +$GIT_WORK_TREE+ set

Yield Parameters:

  • path (String)

    The absolute path used for +$GIT_WORK_TREE+

Since:

  • 0.1.0



202
203
204
205
206
207
208
209
210
# File 'lib/silo/repository.rb', line 202

def in_work_tree(path)
  tmp_dir = path == :tmp
  path = Dir.mktmpdir if tmp_dir
  old_work_tree = ENV['GIT_WORK_TREE']
  ENV['GIT_WORK_TREE'] = path
  Dir.chdir(path) { yield path }
  ENV['GIT_WORK_TREE'] = old_work_tree
  FileUtils.rm_rf path, :secure => true if tmp_dir
end

#info(path) ⇒ Hash<Symbol, Object>

Get information about a file or directory in the repository

Parameters:

  • path (String)

    The path of the file or directory to get information about

Returns:

  • (Hash<Symbol, Object>)

    Information about the requested file or directory.

Since:

  • 0.1.0



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/silo/repository.rb', line 218

def info(path)
  info   = {}
  object = object! path

  info[:history] = history path
  info[:mode]    = object.mode
  info[:name]    = object.name
  info[:path]    = path
  info[:sha]     = object.id

  info[:created]  = info[:history].last.committed_date
  info[:modified] = info[:history].first.committed_date

  if object.is_a? Grit::Blob
    info[:mime] = object.mime_type
    info[:size] = object.size
    info[:type] = :blob
  else
    info[:path] += '/'
    info[:type] = :tree
  end

  info
end

#object!(path) ⇒ Grit::Blob, Grit::Tree

Returns the object (tree or blob) at the given path inside the repository or fail if it does not exist

Parameters:

  • path (String)

    The path of the object in the repository

Returns:

  • (Grit::Blob, Grit::Tree)

    The object at the given path

Raises:

Since:

  • 0.1.0



249
250
251
252
253
# File 'lib/silo/repository.rb', line 249

def object!(path)
  object = find_object path
  raise FileNotFoundError.new(path) if object.nil?
  object
end

#prepareObject

Prepares the Git repository backing this Silo repository for use with Silo

Raises:

Since:

  • 0.1.0



259
260
261
262
263
264
265
266
# File 'lib/silo/repository.rb', line 259

def prepare
  raise AlreadyPreparedError.new(@path) if prepared?
  in_work_tree :tmp do
    FileUtils.touch '.silo'
    @git.add '.silo'
    @git.commit_index 'Enabled Silo for this repository'
  end
end

#prepared?Boolean

Return whether the Git repository backing this Silo repository has already been prepared for use with Silo

Returns:

  • (Boolean)

    The preparation status of the backing Git repository

Since:

  • 0.1.0



272
273
274
# File 'lib/silo/repository.rb', line 272

def prepared?
  !(@git.tree/'.silo').nil?
end

#purge(path, prune = true) ⇒ Object

Purges a single file or the complete structure of a directory with the given path from the repository

WARNING: This will cause a complete rewrite of the repository history and therefore deletes the data completely.

Parameters:

  • path (String)

    The path of the file or directory to purge from the repository

  • prune (Boolean) (defaults to: true)

    Remove empty commits in the Git history

Since:

  • 0.1.0



285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/silo/repository.rb', line 285

def purge(path, prune = true)
  object = object! path
  if object.is_a? Grit::Tree
    (object.blobs + object.trees).each do |blob|
      purge File.join(path, blob.basename), prune
    end
  else
    params = ['-f', '--index-filter',
              "git rm --cached --ignore-unmatch #{path}"]
    params << '--prune-empty' if prune
    params << 'HEAD'
    @git.git.filter_branch({}, *params)
  end
end

#remove(path) ⇒ Object Also known as: rm

Removes a single file or the complete structure of a directory with the given path from the HEAD revision of the repository

NOTE: The data won't be lost as it will be preserved in the history of the Git repository.

Parameters:

  • path (String)

    The path of the file or directory to remove from the repository

Since:

  • 0.1.0



308
309
310
311
312
313
314
315
316
317
# File 'lib/silo/repository.rb', line 308

def remove(path)
  object = object! path
  path += '/' if object.is_a?(Grit::Tree) && path[-1].chr != '/'
  index = @git.index
  index.read_tree 'HEAD'
  index.delete path
  type = object.is_a?(Grit::Tree) ? 'directory' : 'file'
  commit_msg = "Removed #{type} #{path}"
  index.commit commit_msg, [@git.head.commit.sha]
end

#remove_remote(name) ⇒ Object

Removes the remote with the given name from this repository

Parameters:

  • name (String)

    The name of the remote to remove

Raises:

See Also:

Since:

  • 0.1.0



324
325
326
327
328
329
# File 'lib/silo/repository.rb', line 324

def remove_remote(name)
  remote = @remotes[name]
  raise UndefinedRemoteError.new(name) if remote.nil?
  remote.remove
  @remotes[name] = nil
end

#restore(path, prefix = '.') ⇒ Object

Restores a single file or the complete structure of a directory with the given path from the repository

Parameters:

  • path (String)

    The path of the file or directory to restore from the repository

  • prefix (String) (defaults to: '.')

    An optional prefix where the file is restored

Since:

  • 0.1.0



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/silo/repository.rb', line 337

def restore(path, prefix = '.')
  object = object! path
  prefix = File.expand_path prefix
  FileUtils.mkdir_p prefix unless File.exists? prefix

  file_path = File.join prefix, File.basename(path)

  if object.is_a? Grit::Tree
    FileUtils.mkdir file_path unless File.directory? file_path
    (object.blobs + object.trees).each do |obj|
      restore File.join(path, obj.basename), file_path
    end
  else
    file = File.new file_path, 'w'
    file.write object.data
    file.close
  end
end