class Eternity::Repository

Attributes

branches[R]
current[R]
id[R]
locker[R]
name[R]
tracker[R]

Public Class Methods

all() click to toggle source
# File lib/eternity/repository.rb, line 174
def self.all
  sections_count = Eternity.keyspace[:repository].sections.count
  names = Eternity.connection.call('KEYS', Eternity.keyspace[:repository]['*']).map do |key|
    Restruct::Id.new(key).sections[sections_count]
  end.uniq
  names.map { |name| new name }
end
new(name, options={}) click to toggle source
# File lib/eternity/repository.rb, line 6
def initialize(name, options={})
  @name = name.to_s
  @id = Eternity.keyspace[:repository][@name]
  @tracker = Tracker.new self
  @current = Restruct::Hash.new connection: Eternity.connection, id: id[:current]
  @branches = Restruct::Hash.new connection: Eternity.connection, id: id[:branches]
  @locker = Eternity.locker_for @name
  @default_branch = options.fetch(:default_branch, 'master').to_s
end

Public Instance Methods

[](collection) click to toggle source
# File lib/eternity/repository.rb, line 16
def [](collection)
  tracker[collection]
end
branch(name) click to toggle source
# File lib/eternity/repository.rb, line 67
def branch(name)
  raise "Can't branch without commit" unless current_commit?
  raise "Can't branch with uncommitted changes" if changes?

  branches[name] = current_commit.id
end
changes?() click to toggle source
# File lib/eternity/repository.rb, line 24
def changes?
  !tracker.empty?
end
changes_count() click to toggle source
# File lib/eternity/repository.rb, line 28
def changes_count
  tracker.count
end
checkout(options) click to toggle source
# File lib/eternity/repository.rb, line 74
def checkout(options)
  raise "Can't checkout with uncommitted changes" if changes?

  locker.lock! :checkout do
    Eternity.logger.info(self.class) { "Checkout #{name} (#{options.map { |k,v| "#{k}: #{v}" }.join(', ')})" }
    
    original_commit = current_commit

    commit_id, branch = extract_commit_and_branch options

    if commit_id
      raise "Invalid commit #{commit_id}" unless Commit.exists? commit_id
      current[:commit] = commit_id
      branches[branch] = commit_id
    else
      current.delete :commit
      branches.delete branch
    end

    current[:branch] = branch

    Patch.diff original_commit, current_commit
  end
end
commit(options) click to toggle source
# File lib/eternity/repository.rb, line 58
def commit(options)
  raise 'Nothing to commit' unless changes?

  locker.lock! :commit do
    commit! message: options.fetch(:message), 
            author:  options.fetch(:author)
  end
end
current_branch() click to toggle source
# File lib/eternity/repository.rb, line 54
def current_branch
  current[:branch] || @default_branch
end
current_commit() click to toggle source
# File lib/eternity/repository.rb, line 50
def current_commit
  Commit.new current[:commit]
end
current_commit?() click to toggle source
# File lib/eternity/repository.rb, line 46
def current_commit?
  current.key? :commit
end
delta() click to toggle source
# File lib/eternity/repository.rb, line 32
def delta
  tracker.flatten
end
delta=(delta) click to toggle source
# File lib/eternity/repository.rb, line 36
def delta=(delta)
  tracker.clear
  delta.each do |collection, changes|
    changes.each do |id, change|
      args = [id, change['data']].compact
      self[collection].send(change['action'], *args)
    end
  end
end
destroy() click to toggle source
# File lib/eternity/repository.rb, line 153
def destroy
  tracker.destroy
  current.destroy
  branches.destroy
end
dump()
Alias for: to_h
empty?() click to toggle source
# File lib/eternity/repository.rb, line 20
def empty?
  tracker.empty? && current.empty? && branches.empty?
end
log() click to toggle source
# File lib/eternity/repository.rb, line 149
def log
  current_commit? ? ([current_commit] + current_commit.history) : []
end
merge(options) click to toggle source
# File lib/eternity/repository.rb, line 99
def merge(options)
  raise "Can't merge with uncommitted changes" if changes?

  commit_id = extract_commit options

  raise "Invalid commit #{commit_id}" unless Commit.exists? commit_id

  merge! Commit.new(commit_id)
end
pull() click to toggle source
# File lib/eternity/repository.rb, line 122
def pull
  raise "Can't pull with uncommitted changes" if changes?
  raise "Branch not found: #{current_branch}" unless Branch.exists? current_branch

  target_commit = Branch[current_branch]

  Eternity.logger.info(self.class) { "Pull #{name} (#{target_commit.id})" }

  if current_commit == target_commit || current_commit.fast_forward?(target_commit)
    Patch.merge current_commit, target_commit
  elsif target_commit.fast_forward?(current_commit)
    checkout commit: target_commit.id
  else 
    merge! target_commit
  end
end
push() click to toggle source
# File lib/eternity/repository.rb, line 109
def push
  raise 'Push rejected (non fast forward)' if current_commit != Branch[current_branch] && !current_commit.fast_forward?(Branch[current_branch])
  push!
end
push!() click to toggle source
# File lib/eternity/repository.rb, line 114
def push!
  raise "Can't push without commit" unless current_commit?

  Eternity.logger.info(self.class) { "Push #{name} (#{current_commit.id})" }

  Branch[current_branch] = current_commit.id
end
restore(dump) click to toggle source
# File lib/eternity/repository.rb, line 168
def restore(dump)
  current.merge! dump['current']
  branches.merge! dump['branches']
  self.delta = dump['delta']
end
revert() click to toggle source
# File lib/eternity/repository.rb, line 139
def revert
  locker.lock! :revert do
    Eternity.logger.info(self.class) { "Revert #{name}" }

    current_commit.with_index do |index|
      Delta.revert(delta, index).tap { tracker.revert }
    end
  end
end
to_h() click to toggle source
# File lib/eternity/repository.rb, line 159
def to_h
  {
    'current' => current.to_h,
    'branches' => branches.to_h,
    'delta' => delta
  }
end
Also aliased as: dump

Private Instance Methods

commit!(options) click to toggle source
# File lib/eternity/repository.rb, line 186
def commit!(options)
  Eternity.logger.info(self.class) { "Commit #{name} (author: #{options[:author]}, message: #{options[:message]})" }

  changes = delta
  options[:parents] ||= [current_commit.id]
  options[:delta]   ||= write_delta changes
  options[:index]   ||= write_index changes

  Commit.create(options).tap do |commit|
    current[:commit] = commit.id
    current[:branch] = current_branch
    branches[current_branch] = commit.id
    tracker.clear
  end
end
extract_commit(options) click to toggle source
# File lib/eternity/repository.rb, line 231
def extract_commit(options)
  extract_commit_and_branch(options).first
end
extract_commit_and_branch(options) click to toggle source
# File lib/eternity/repository.rb, line 235
def extract_commit_and_branch(options)
  branch = options.fetch(:branch) { current_branch }
  
  commit_id = options.fetch(:commit) do
    if branches.key? branch
      branches[branch]
    elsif Branch.exists?(branch)
      Branch[branch].id
    else
      raise "Invalid branch #{branch}"
    end
  end

  [commit_id, branch]
end
merge!(target_commit) click to toggle source
# File lib/eternity/repository.rb, line 202
def merge!(target_commit)
  locker.lock! :merge do
    Eternity.logger.info(self.class) { "Merge #{name} (#{target_commit.short_id} into #{current_commit.short_id})" }

    patch = Patch.merge current_commit, target_commit

    raise 'Already merged' if patch.merged?

    commit! message: "Merge #{target_commit.short_id} into #{current_commit.short_id} (#{name})",
            author:  'System',
            parents: [current_commit.id, target_commit.id],
            index:   write_index(patch.delta),
            base:    patch.base_commit.id

    patch
  end
end
write_delta(delta) click to toggle source
# File lib/eternity/repository.rb, line 227
def write_delta(delta)
  Blob.write :delta, delta
end
write_index(delta) click to toggle source
# File lib/eternity/repository.rb, line 220
def write_index(delta)
  current_commit.with_index do |index|
    index.apply delta
    index.write_blob
  end
end