Class: GitKeyvalue::KeyValueRepo

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

Overview

Provides a GET/PUT-style interface for a git repo. In effect, it presents the repo as a key/value store, where the keys are file paths (relative to the repo’s root) and the values are the contents of those files.

Requirements

Known good with ruby 1.9.3 and git 1.7.9.6.

Performance & resource usage

Not performant. Must clone the repo before performing any operations. Needs whatever disk space is required for a repo clone. Clears this space when the object is destroyed.

Object lifetime

Stores the local repo in the OS’s temporary directory. Therefore, you should not expect this object to remain valid across automated housekeeping events that might destroy this directory.

Footnote on shallow cloning

Okay, technically, this does not clone the entire repo. For better performance it does a “shallow clone” of the repo, which grabs only the files necessary to represent the HEAD commit. Such a shallow clone is officially enough to enable GET operations, which read only those files anyway. However, according to the git-clone docs, the shallow clone is not officially enough to enable git-push to update those files on the remote repo. However, this seems like a bug in the git-clone docs since, in reality, a shallow clone is enough and should be enough for pushing new commits, since a new commit only needs to reference its parent commit(s).

The bottom line: by using shallow cloning for better perf, this class is relying on undocumented behavior in git-push. This works fine as of git version 1.7.9.6. I see no reason to expect this to break in the future, since this undocumented behavior follows directly from git’s data model, which is stable. However, if it does break, and you want to switch to using the documented git behavior, then set USE_SHALLOW_CLONING to false.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(repo_url) ⇒ KeyValueRepo

Clones the remote repo, failing if it is invalid or inaccessible.

As it clones the entire repo, this may take a long time if you are manipulating a large remote repo. Keeps the repo in the OS’s temporary directory, so you should not expect this object to remain valid across automated cleanups of that temporary directory (which happen, for instance, typically at restart).

Parameters:

  • repo_url (String)

    URL of a valid, network-accessible, permissions-accessible git repo

Raises:



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/git_keyvalue.rb', line 185

def initialize(repo_url)
  @repo_url = repo_url
  @path_to_repo = Dir.mktmpdir('KeyValueGitTempDir')
  if USE_SHALLOW_CLONING
    # experimental variant. uses undocumented behaviour of
    # git-clone. This is because setting --depth 1 produces a
    # shallow clone, which according to the docs does not let you
    # git-push aftewards, but in reality should and does let you
    # git-push. This is a bug in the git documentation.
    success = system('git','clone','--depth','1',@repo_url,@path_to_repo)  
  else
    # stable variant. uses documented behavior of git-clone
    success = system('git','clone',@repo_url,@path_to_repo) 
  end
  if not success
    raise KeyValueGitError, 'Failed to initialize, because could not clone the remote repo: ' + repo_url + '. Please verify this is a valid git URL, and that any required network connection or login credentials are available.'
  end
  ObjectSpace.define_finalizer(self, self.class.make_finalizer(@path_to_repo))
end

Instance Attribute Details

#path_to_repoString (readonly)

Returns absolute filesystem path of the local repo.

Returns:

  • (String)

    absolute filesystem path of the local repo



171
172
173
# File 'lib/git_keyvalue.rb', line 171

def path_to_repo
  @path_to_repo
end

#repo_urlString (readonly)

Returns URL of the remote git repo.

Returns:

  • (String)

    URL of the remote git repo



169
170
171
# File 'lib/git_keyvalue.rb', line 169

def repo_url
  @repo_url
end

Instance Method Details

#get(path_in_repo) ⇒ String?

Get contents of a file, or nil if it does not exist

Parameters:

  • path_in_repo (String)

    relative path of repo file to get

Returns:

  • (String, nil)

    string contents of file, or nil if non-existent



211
212
213
# File 'lib/git_keyvalue.rb', line 211

def get(path_in_repo)
  outer_get(path_in_repo) { |abspath| File.read(abspath) }
end

#getfile(path_in_repo, dest_path) ⇒ Object

Copies the repo file at path_in_repo to dest_path

Does no validation regarding dest_path. If dest_path points to a file, it will overwrite that file. If it points to a directory, it will copy into that directory.

Parameters:

  • path_in_repo (String)

    relative path of repo file to get

  • dest_path (String)

    path to which to copy the gotten file



224
225
226
# File 'lib/git_keyvalue.rb', line 224

def getfile(path_in_repo, dest_path)
  outer_get(path_in_repo) { |abspath| FileUtils.cp(abspath, dest_path) }
end

#put(path_in_repo, string_value) ⇒ Object

Sets the contents of the file at path_in_repo, creating it if necessary

Parameters:

  • path_in_repo (String)

    relative path of repo file to add or update

  • string_value (String)

    the new contents for the file at this path



234
235
236
237
238
239
240
241
242
# File 'lib/git_keyvalue.rb', line 234

def put(path_in_repo, string_value)
  path_in_repo = blindly_relativize_path(path_in_repo)
  outer_put(path_in_repo) {
    # create parent directories if needed
    FileUtils.mkdir_p(File.dirname(path_in_repo))
    # write new file contents
    File.open(path_in_repo,'w') { |f| f.write(string_value) }
  }    
end

#putfile(path_in_repo, src_file_path) ⇒ Object

Sets the contents of the file at path, creating it if necessary

Parameters:

  • path_in_repo (String)

    relative path of repo file to add or update

  • src_file_path (String)

    file to use for replacing path_in_repo



250
251
252
253
254
255
256
257
258
259
# File 'lib/git_keyvalue.rb', line 250

def putfile(path_in_repo, src_file_path)
  path_in_repo = blindly_relativize_path(path_in_repo)
  outer_put(path_in_repo) {
    # create parent directories if needed
    FileUtils.mkdir_p(File.dirname(path_in_repo))
    # copy file at src_file_path into the path_in_repo
    abspath = Pathname.new(File.join(@path_to_repo,path_in_repo)).to_s
    FileUtils.cp(src_file_path, abspath)
  }
end