class GitDS::Database

Actually DbConnection to the repository.

Note: all operations should be in exec or transaction blocks. These use a persistent staging index, and are more efficient.

Attributes

actor[RW]

Actor to use when performing Database operations. All Transaction and ExecCmd objects will use this actor by default.

Default is nil (i.e. let Git read actor from .git/config or ENV).

stale[R]

Flag to mark if database has been closed (i.e. connection is invalid).

subscribers[R]

Subcribers that are notified when the model is changed. This is a Hash of an ident (e.g. a classname, UUID, or symbol) to a 2-element array: a callback method and an (optional) object to pass with that method.

Public Class Methods

connect(path, create=true) click to toggle source

Open a connection to database.

If ‘create’ is true (the default), a database will be created if one does not exist at ‘path’.

# File lib/git-ds/database.rb, line 87
def self.connect(path, create=true)
  return nil if (not create) && (not File.exist? path)
  connect_as(path, nil, nil, create)
end
connect_as(path, username, email, create=true) click to toggle source

Connect to a git database as the specified user.

# File lib/git-ds/database.rb, line 95
def self.connect_as(path, username, email, create=true)
  return nil if (not create) && (not File.exist? path)
  new(path, username, email)
end
new(path, username=nil, email=nil) click to toggle source

Return a connection to the Git DB. Creates the DB if it does not already exist.

Calls superclass method
# File lib/git-ds/database.rb, line 57
def initialize(path, username=nil, email=nil)
  @stale = true         # DB is always stale until it is initialized

  init = false
  if not File.exist? path
    Repo.create(path)
    init = true
  end

  super(path)
  @stale = false        # DB is connected!

  if init
    # initial commit is needed for branches to work smoothly
    stage { |idx| idx.add('.git-ds/version', "1.0\n") }
    staging.commit('Database initialized.')
    unstage
  end

  @actor = Grit::Actor.new(username, email) if username
  @subscribers = {}

end

Public Instance Methods

add(path, data='', on_fs=false) click to toggle source

# ———————————————————————- Add files to the database. Calls exec to ensure that a write is not performed if a staging index already exists.

# File lib/git-ds/database.rb, line 237
def add(path, data='', on_fs=false)
  exec { index.add(path, data, on_fs) }
end
batch() { |staging| ... } click to toggle source

Execute a block using an in-memory Staging index.

This is an optimization. It writes the current index to the git staging index on disk, replaces it with an in-memory index that DOES NOT write to the object tree on disk, invokes the block, writes the in-memory index to the git staging index on disk, then reads the staging index into the repo/database and makes it the current index.

The idea is to reduce disk writes caused by exec and transaction, which can end up being very costly when nested.

NOTE: branch-and-merge will fail if in batch mode (TODO: FIX).

# File lib/git-ds/database.rb, line 355
def batch(&block)
  # NOTE: the use of 'self.staging' is quite important in this method.

  # write current index to git-staging-index
  idx = self.staging? ? self.staging : nil
  idx.sync if idx

  # replace current index with an in-mem staging index
  unstage
  self.staging=StageMemIndex.read(self)

  begin
    yield staging if block_given?

  rescue Exception => e
    # ensure index is discarded if there is a problem
    unstage
    self.staging if idx
    raise e
  end

  # write in-mem staging index to git-staging-index
  self.staging.force_sync

  # read git-staging-index if appropriate
  unstage
  self.staging if idx
end
branch_and_merge(name=next_branch_tag(), actor=nil, &block) click to toggle source

Branch-and-merge: Run block in a transaction under a new branch. If the transaction succeeds, the branch is merged back into master.

See Database#transaction .

# File lib/git-ds/database.rb, line 307
def branch_and_merge(name=next_branch_tag(), actor=nil, &block)
  raise InvalidDbError if @stale

  # Force a commit before the merge
  # TODO: determine if this is really necessary
  staging.sync
  staging.commit('auto-commit before branch-and-merge', self.actor)

  # ensure staging index is nil [in case branch name was re-used]
  unstage

  # save old actor
  old_actor = self.actor
  self.actor = actor if actor

  sha = commits.last ? commits.last.id : nil
  tag = create_branch(name, sha)
  set_branch(tag, self.actor)

  # execute block in a transaction
  rv = true
  begin
    transaction(&block)
    merge_branch(tag, self.actor)
  rescue Exception =>e
    rv = false
  end

  # restore actor
  self.actor = old_actor if actor

  rv
end
close(save=true) click to toggle source

Close DB connection, writing all changes to disk.

NOTE: This does not create a commit! Ony the staging index changes.

# File lib/git-ds/database.rb, line 105
def close(save=true)
  raise InvalidDbError if @stale

  if save && staging?
    self.staging.write
  end
  unstage
  @stale = true

  # TODO: remove all locks etc
end
config() click to toggle source

Provides access to the Hash of Git-DS config variables.

# File lib/git-ds/database.rb, line 144
def config
  @git_config ||= RepoConfig.new(self, 'git-ds')
end
Also aliased as: repo_config
connected?()
Alias for: valid?
delete(path) click to toggle source

Delete an object from the database.

# File lib/git-ds/database.rb, line 257
def delete(path)
  exec { index.delete(path) }
end
exec(&block) click to toggle source

# ———————————————————————- Execute a block in the context of the staging index.

See ExecCmd.

# File lib/git-ds/database.rb, line 200
def exec(&block)
  raise InvalidDbError if @stale

  return exec_in_staging(true, &block) if self.staging?

  begin
    self.staging
    exec_in_staging(false, &block)
    self.staging.write
  ensure
    self.unstage
  end

end
fast_add(path, data='', on_fs=false) click to toggle source

Add files to the database without using ExecCmd or Transaction. Care must be taken in using this as it does not sync/build the staging index, so it must be wrapped in an ExecCmd or Transaction, or the index must be synced/built after all of the fast_add calls are complete.

See Database#add.

# File lib/git-ds/database.rb, line 249
def fast_add(path, data='', on_fs=false)
  # TODO: verify that this will suffice
  index.add(path, data, on_fs)
end
head() click to toggle source

Wrapper for Grit::Repo#head that checks if Database has been closed.

Calls superclass method
# File lib/git-ds/database.rb, line 280
def head
  raise InvalidDbError if @stale
  super
end
index_new() click to toggle source

Wrapper for Grit::Repo#index that checks if Database has been closed.

Calls superclass method
# File lib/git-ds/database.rb, line 264
def index_new
  raise InvalidDbError if @stale
  super
end
mark(msg) click to toggle source

Generate a tag object for the most recent commit.

# File lib/git-ds/database.rb, line 296
def mark(msg)
  tag_object(msg, commits.last.id)
end
notify() click to toggle source

Notify all subscribers that a change has occurred.

# File lib/git-ds/database.rb, line 183
def notify
  @subscribers.each { |ident, (block,obj)| block.call(obj) }
end
purge() click to toggle source

Delete Database (including entire repository) from disk.

# File lib/git-ds/database.rb, line 129
def purge
  raise InvalidDbError if @stale

  close(false)
  FileUtils.remove_dir(@path) if ::File.exist?(@path)
end
repo_config()

Grit::Repo#config is wrapped by Database#config.

Alias for: config
set_author(name, email=nil) click to toggle source

Set the Git author information for the database connection. Wrapper for actor=.

# File lib/git-ds/database.rb, line 152
def set_author(name, email=nil)
  self.actor = name ? Grit::Actor.new(name, (email ? email : '')) : nil
end
staging() click to toggle source

Wrapper for Grit::Repo#staging that checks if Database has been closed.

Calls superclass method
# File lib/git-ds/database.rb, line 272
def staging
  raise InvalidDbError if @stale
  super
end
subscribe(ident, obj=nil, func=nil, &block) click to toggle source

# ———————————————————————- Subscribe to change notifications from the model. The provided callback will be invoked whenever the model is modified (specifically, when an outer Transaction or ExecCmd is completed).

A subscriber can use either block or argument syntax:

def func_cb(arg)
  ...
end
model.subscribe( self.ident, arg, func_cb )

# block callback
model.subscribe( self.ident ) { ... }

# block callback where arg is specified in advance
model.subscribe( self.ident, arg ) { |arg| ... }
# File lib/git-ds/database.rb, line 175
def subscribe(ident, obj=nil, func=nil, &block)
  cb = (block_given?) ? block : func
  @subscribers[ident] = [cb, obj]
end
transaction(&block) click to toggle source

Execute a transaction in the context of the staging index.

See Transaction.

# File lib/git-ds/database.rb, line 220
def transaction(&block)
  raise InvalidDbError if @stale

  return transaction_in_staging(true, &block) if self.staging?

  begin
    transaction_in_staging(false, &block)
  ensure
    self.unstage
  end
end
tree(treeish = 'master', paths = []) click to toggle source

Wrapper for Grit::Repo#tree that checks if Database has been closed.

Calls superclass method
# File lib/git-ds/database.rb, line 288
def tree(treeish = 'master', paths = [])
  raise InvalidDbError if @stale
  super
end
unsubscribe(ident) click to toggle source

Unsubscribe from change notification.

# File lib/git-ds/database.rb, line 190
def unsubscribe(ident)
  @subscribers.delete(ident)
end
valid?() click to toggle source

Return true if the database is valid (i.e. open)

# File lib/git-ds/database.rb, line 120
def valid?
  @stale == false
end
Also aliased as: connected?

Private Instance Methods

exec_in_staging(nested, &block) click to toggle source

Execute code block in context of current DB index

# File lib/git-ds/database.rb, line 390
def exec_in_staging(nested, &block)
  cmd = ExecCmd.new(self.staging, nested, &block)
  cmd.actor = self.actor
  cmd.perform
end
transaction_in_staging(nested, &block) click to toggle source

Perform transaction in context of current DB index

# File lib/git-ds/database.rb, line 399
def transaction_in_staging(nested, &block)
  t = Transaction.new(self.staging, nested, &block)
  t.actor = self.actor
  t.perform
end