Class: GitDS::Database

Inherits:
Repo
  • Object
show all
Defined in:
lib/git-ds/database.rb

Overview

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.

Constant Summary

Constants inherited from Repo

Repo::DEFAULT_BRANCH, Repo::DEFAULT_TAG, Repo::GIT_DIR

Instance Attribute Summary collapse

Attributes inherited from Repo

#current_branch, #last_branch_tag, #path

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Repo

#add_files, #branch, #clean_tag, create, #create_branch, #exec_git_cmd, #exec_in_git_dir, #include?, #list, #list_blobs, #list_trees, #merge_branch, #next_branch_tag, #object_data, #path_to_object, #path_to_sha, #raw_tree, #root_sha, #set_branch, #stage, #stage_and_commit, #staging=, #staging?, #tag_object, top_level, #top_level, #tree_contents, #unstage

Constructor Details

#initialize(path, username = nil, email = nil) ⇒ Database

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



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# 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

Instance Attribute Details

#actorObject

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).



44
45
46
# File 'lib/git-ds/database.rb', line 44

def actor
  @actor
end

#staleObject (readonly)

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



36
37
38
# File 'lib/git-ds/database.rb', line 36

def stale
  @stale
end

#subscribersObject (readonly)

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.



51
52
53
# File 'lib/git-ds/database.rb', line 51

def subscribers
  @subscribers
end

Class Method Details

.connect(path, create = true) ⇒ Object

Open a connection to database.

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



87
88
89
90
# 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) ⇒ Object

Connect to a git database as the specified user.



95
96
97
98
# 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

Instance Method Details

#add(path, data = '', on_fs = false) ⇒ Object

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



237
238
239
# File 'lib/git-ds/database.rb', line 237

def add(path, data='', on_fs=false)
  exec { index.add(path, data, on_fs) }
end

#batch(&block) ⇒ Object

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).



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# 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) ⇒ Object

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 .

Raises:



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# 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) ⇒ Object

Close DB connection, writing all changes to disk.

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

Raises:



105
106
107
108
109
110
111
112
113
114
115
# 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

#configObject

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



144
145
146
# File 'lib/git-ds/database.rb', line 144

def config
  @git_config ||= RepoConfig.new(self, 'git-ds')
end

#delete(path) ⇒ Object

Delete an object from the database.



257
258
259
# File 'lib/git-ds/database.rb', line 257

def delete(path)
  exec { index.delete(path) }
end

#exec(&block) ⇒ Object

Execute a block in the context of the staging index.

See ExecCmd.

Raises:



200
201
202
203
204
205
206
207
208
209
210
211
212
213
# 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) ⇒ Object

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.



249
250
251
252
# 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

#headObject

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

Raises:



280
281
282
283
# File 'lib/git-ds/database.rb', line 280

def head
  raise InvalidDbError if @stale
  super
end

#index_newObject

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

Raises:



264
265
266
267
# File 'lib/git-ds/database.rb', line 264

def index_new
  raise InvalidDbError if @stale
  super
end

#mark(msg) ⇒ Object

Generate a tag object for the most recent commit.



296
297
298
# File 'lib/git-ds/database.rb', line 296

def mark(msg)
  tag_object(msg, commits.last.id)
end

#notifyObject

Notify all subscribers that a change has occurred.



183
184
185
# File 'lib/git-ds/database.rb', line 183

def notify
  @subscribers.each { |ident, (block,obj)| block.call(obj) }
end

#purgeObject

Delete Database (including entire repository) from disk.

Raises:



129
130
131
132
133
134
# 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_configObject

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



139
# File 'lib/git-ds/database.rb', line 139

alias :repo_config :config

#set_author(name, email = nil) ⇒ Object

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



152
153
154
# 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

#stagingObject

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

Raises:



272
273
274
275
# File 'lib/git-ds/database.rb', line 272

def staging
  raise InvalidDbError if @stale
  super
end

#subscribe(ident, obj = nil, func = nil, &block) ⇒ Object

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| ... }


175
176
177
178
# 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) ⇒ Object

Execute a transaction in the context of the staging index.

See Transaction.

Raises:



220
221
222
223
224
225
226
227
228
229
230
# 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 = []) ⇒ Object

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

Raises:



288
289
290
291
# File 'lib/git-ds/database.rb', line 288

def tree(treeish = 'master', paths = [])
  raise InvalidDbError if @stale
  super
end

#unsubscribe(ident) ⇒ Object

Unsubscribe from change notification.



190
191
192
# File 'lib/git-ds/database.rb', line 190

def unsubscribe(ident)
  @subscribers.delete(ident)
end

#valid?Boolean Also known as: connected?

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

Returns:

  • (Boolean)


120
121
122
# File 'lib/git-ds/database.rb', line 120

def valid?
  @stale == false
end