Class: GitDS::Repo

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

Overview

A Git repository.

Note: StagingIndex is cached, as it is from the command line.

Direct Known Subclasses

Database

Constant Summary collapse

GIT_DIR =
::File::SEPARATOR + '.git'
DEFAULT_TAG =
'0.0.0'
DEFAULT_BRANCH =
'master'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ Repo

Initialize a repo from the .git subdir in the given path.



70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/git-ds/repo.rb', line 70

def initialize(path)
  path.chomp(GIT_DIR) if path.end_with? GIT_DIR
  @path = (path.empty?) ? '.' : path

  # TODO: get last branch tag from repo
  #       prob as a git-config
  @last_branch_tag = DEFAULT_TAG.dup
  @current_branch = DEFAULT_BRANCH.dup
  @staging_index = nil
  @saved_stages = {}

  super(@path + GIT_DIR)
end

Instance Attribute Details

#current_branchObject (readonly)

Returns the value of attribute current_branch.



38
39
40
# File 'lib/git-ds/repo.rb', line 38

def current_branch
  @current_branch
end

#last_branch_tagObject (readonly)

Returns the value of attribute last_branch_tag.



35
36
37
# File 'lib/git-ds/repo.rb', line 35

def last_branch_tag
  @last_branch_tag
end

#pathObject (readonly)

Returns the value of attribute path.



40
41
42
# File 'lib/git-ds/repo.rb', line 40

def path
  @path
end

Class Method Details

.create(path) ⇒ Object

TODO: something more intelligent, e.g. with git/repo options



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

def self.create(path)
  Grit::Repo.init(path)
  self.new(path)          # discard Grit::Repo object and use a Repo object
end

.top_level(path = '.') ⇒ Object

Return the top-level directory of the repo containing the location ‘path’.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/git-ds/repo.rb', line 51

def self.top_level(path='.')
  local = (path == '.')
  old_dir = nil

  if path != '.'
    old_dir = Dir.getwd
    dest = (File.directory? path) ? path : File.dirname(path)
    Dir.chdir dest
  end

  dir = `git rev-parse --show-toplevel`
  Dir.chdir old_dir if old_dir

  (dir == '.git') ? '.' : dir.chomp(GIT_DIR) 
end

Instance Method Details

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

Add a DB entry at the filesystem path ‘path’ with contents ‘data’. If ‘on_fs’ is true, the file is created in the filesystem as well. This uses the staging index.



314
315
316
# File 'lib/git-ds/repo.rb', line 314

def add(path, data='', on_fs=false)
  self.stage { |idx| idx.add(path, data, on_fs) }
end

#add_filesObject




307
# File 'lib/git-ds/repo.rb', line 307

alias :add_files :add

#branch(tag = @current_branch) ⇒ Object

Return the Head object for the specified branch



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

def branch(tag=@current_branch)
  get_head(tag)
end

#clean_tag(name) ⇒ Object

Return a cleaned-up version of the tag name, suitable for use as a filename. Replaces all non-alphanumeric characters (except “-.,”) with “_”.



105
106
107
# File 'lib/git-ds/repo.rb', line 105

def clean_tag(name)
  name.gsub( /[^-.,_[:alnum:]]/, '_' )
end

#create_branch(tag = next_branch_tag(), sha = nil) ⇒ Object

Creates a branch in refs/heads and associates it with the specified commit. If sha is nil, the latest commit from ‘master’ is used. The canonical name of the tag is returned.



129
130
131
132
133
134
135
136
137
# File 'lib/git-ds/repo.rb', line 129

def create_branch( tag=next_branch_tag(), sha=nil )
  if not sha
    sha = commits.first.id
    #sha = branches.first.commit.id if not sha
  end
  name = clean_tag(tag)
  update_ref(name, sha)
  name
end

#delete(path) ⇒ Object

Remove an object from the database. This can be a path to a Tree or a Blob.



321
322
323
# File 'lib/git-ds/repo.rb', line 321

def delete(path)
  self.stage { |idx| idx.delete(path) }
end

#exec_git_cmd(cmd, actor = nil) ⇒ Object

Execute the specified command using Repo#exec_in_git_dir.



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/git-ds/repo.rb', line 276

def exec_git_cmd( cmd, actor=nil )
  old_aname = ENV['GIT_AUTHOR_NAME']
  old_aemail = ENV['GIT_AUTHOR_EMAIL']
  old_cname = ENV['GIT_COMMITTER_NAME']
  old_cemail = ENV['GIT_COMMITTER_EMAIL']
  old_pager = ENV['GIT_PAGER']

  if actor
    ENV['GIT_AUTHOR_NAME'] = actor.name
    ENV['GIT_AUTHOR_EMAIL'] = actor.email
    ENV['GIT_COMMITTER_NAME'] = actor.name
    ENV['GIT_COMMITTER_EMAIL'] = actor.email
  end
  ENV['GIT_PAGER'] = ''

  # Note: we cannot use Grit#raw_git_call as it requires an index file
  rv = exec_in_git_dir do 
    `#{cmd}`
    raise CommandError, rv if $? != 0
  end

  ENV['GIT_AUTHOR_NAME'] = old_aname
  ENV['GIT_AUTHOR_EMAIL'] = old_aemail
  ENV['GIT_COMMITTER_NAME'] = old_cname
  ENV['GIT_COMMITTER_EMAIL'] = old_cemail
  ENV['GIT_PAGER'] = old_pager

  rv
end

#exec_in_git_dir(&block) ⇒ Object

Change to the Repo#top_level dir, yield to block, then pop the dir stack.



265
266
267
268
269
270
271
# File 'lib/git-ds/repo.rb', line 265

def exec_in_git_dir(&block)
  curr = Dir.getwd
  Dir.chdir top_level
  result = yield
  Dir.chdir curr
  result
end

#include?(path) ⇒ Boolean Also known as: exist?

Return true if path exists in repo (on fs or in-tree)

Returns:

  • (Boolean)


94
95
96
# File 'lib/git-ds/repo.rb', line 94

def include?(path)
  path_to_object(path) ? true : false
end

#index_newObject

Return an empty git index for the repo.



198
199
200
# File 'lib/git-ds/repo.rb', line 198

def index_new
  Index.new(self)
end

#list(path = nil) ⇒ Object

Return a Hash of the contents of ‘path’. This is just a wrapper for tree_contents.



406
407
408
409
410
411
# File 'lib/git-ds/repo.rb', line 406

def list(path=nil)
  sha = path_to_sha(path)
  # ensure correct operation even if path doesn't exist in repo
  t = sha ? tree(sha) : tree('master', (path ? [path] : []))
  t ? tree_contents( t ) : {}
end

#list_blobs(path = '') ⇒ Object

Return Hash of all Blob child objects at ‘path’.



416
417
418
# File 'lib/git-ds/repo.rb', line 416

def list_blobs(path='')
  list(path).delete_if { |k,v| not v.kind_of?(Grit::Blob) }
end

#list_trees(path = '') ⇒ Object

Return Hash of all Tree child objects at ‘path’.



423
424
425
# File 'lib/git-ds/repo.rb', line 423

def list_trees(path='')
  list(path).delete_if { |k,v| not v.kind_of?(Grit::Tree) }
end

#merge_branch(tag = @current_branch, actor = nil) ⇒ Object

Merge specified branch into master.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/git-ds/repo.rb', line 166

def merge_branch( tag=@current_branch, actor=nil )
  raise "Invalid branch '#{tag}'" if not (is_head? tag)

  tag.gsub!(/['\\]/, '')

  # switch to master branch
  set_branch(DEFAULT_BRANCH, actor)

  # merge target branch to master branch

  rv = nil
  begin
    rv = exec_git_cmd("git merge -n --no-ff --no-log --no-squash '#{tag}'", 
                      actor)
  rescue CommandError => e
    $stderr.puts e.message
  end
  rv
end

#next_branch_tagObject

Returns the next value for a tag. This is primarily used to auto-generate tag names, e.g. 1.0.1, 1.0.2, etc.



113
114
115
# File 'lib/git-ds/repo.rb', line 113

def next_branch_tag
  @last_branch_tag.succ!
end

#object_data(path) ⇒ Object

Fetch the contents of a DB or FS object from the object database. This uses the staging index.



329
330
331
332
# File 'lib/git-ds/repo.rb', line 329

def object_data(path)
  blob = path_to_object(path)
  (blob && blob.kind_of?(Grit::Blob)) ? blob.data : nil
end

#path_to_object(path) ⇒ Object

Fetch an object from the repo based on its path.

If a staging index exists, its tree will be used; otherwise, the tree ‘master’ will be used.

The object returned will be a Grit::Blob, a Grit::Tree, or nil.



446
447
448
449
450
451
452
# File 'lib/git-ds/repo.rb', line 446

def path_to_object(path)
  treeish = (@staging_index ? staging.sha : 'master')
  tree = self.tree(treeish, [path])
  return tree.blobs.first if tree && (not tree.blobs.empty?)
  return tree.trees.first if tree && (not tree.trees.empty?)
  nil
end

#path_to_sha(path, head = @current_branch) ⇒ Object

Return the SHA1 of ‘path’ in repo. Uses staging index if present.



366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/git-ds/repo.rb', line 366

def path_to_sha(path, head=@current_branch)
  # Return the root of the repo if no path is specified
  return root_sha(head) if (not path) || (path.empty?)

  if staging?
    @staging_index.sync
    head = @staging_index.current_tree.id
  end

  dir = tree(head, [path])
  (dir && dir.contents.length > 0) ? dir.contents.first.id : nil
end

#raw_tree(path, recursive = false) ⇒ Object

Returns the raw (git cat-file) representation of a tree.



430
431
432
433
434
435
436
# File 'lib/git-ds/repo.rb', line 430

def raw_tree(path, recursive=false)
  # Note: to construct recursive tree: 
  #   Tree.allocate.construct_initialize(repo, treeish, output)
  #   from repo.git.ls_tree or raw_tree
  sha = path_to_sha(path)
  sha ? git.ruby_git.get_raw_tree( sha, recursive ) : ''
end

#root_sha(head = @current_branch) ⇒ Object

Return the SHA of the root Tree in the repository.

Uses the staging index if it is active.



384
385
386
387
388
389
390
391
# File 'lib/git-ds/repo.rb', line 384

def root_sha(head=@current_branch)
  if staging?
    @staging_index.sync
    return @staging_index.sha
  end

  (self.commits.count > 0) ? self.commits.first.tree.id : nil
end

#set_branch(tag, actor = nil) ⇒ Object Also known as: branch=

Sets the current branch to the specified tag. This changes the default branch for all repo activity and sets HEAD.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/git-ds/repo.rb', line 143

def set_branch( tag, actor=nil )
  # allow creating of new branches
  opt = (is_head? tag) ? '' : '-b'

  # Save staging index for current branch
  @saved_stages[@current_branch] = self.staging if self.staging?

  exec_git_cmd( "git checkout -q -m #{opt} '#{tag}'", actor )

  # Synchronize staging index (required before merge)
  unstage
  self.staging.sync

  # Update current_branch info and restore staging for branch
  self.staging = @saved_stages[tag]
  @current_branch = tag
end

#stage(&block) ⇒ Object

Yield staging index to the provided block, then write the index when the block returns. This allows the Git staging index to be modified from within Ruby, with all changes being visible to the Git command-line tools. Returns staging index for chaining purposes.



244
245
246
247
248
249
# File 'lib/git-ds/repo.rb', line 244

def stage(&block)
  idx = self.staging
  rv = yield idx
  idx.build
  rv
end

#stage_and_commit(msg, actor = nil, &block) ⇒ Object

Read the Git staging index, then commit it with the provided message and author info. Returns SHA of commit.



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

def stage_and_commit(msg, actor=nil, &block)
  stage(&block)
  self.staging.commit(msg, actor)
end

#stagingObject Also known as: index

Return the staging index for the repo.



205
206
207
208
# File 'lib/git-ds/repo.rb', line 205

def staging
  # TODO: mutex
  @staging_index ||= StageIndex.new(self)
end

#staging=(idx) ⇒ Object Also known as: index=

Set the staging index. This can be used to clear the staging index, or to use a specific index as the staging index.



214
215
216
217
# File 'lib/git-ds/repo.rb', line 214

def staging=(idx)
  # TODO: mutex
  @staging_index = idx
end

#staging?Boolean

Return true if a staging index is active.

Returns:

  • (Boolean)


230
231
232
# File 'lib/git-ds/repo.rb', line 230

def staging?
  @staging_index != nil
end

#tag_object(tag, sha) ⇒ Object

Tag (name) an object, e.g. a commit.



189
190
191
# File 'lib/git-ds/repo.rb', line 189

def tag_object(tag, sha)
  git.fs_write("refs/tags/#{clean_tag(tag)}", sha)
end

#top_levelObject

Return the top-level directory of the repo (the parent of .git).



87
88
89
# File 'lib/git-ds/repo.rb', line 87

def top_level
  git.git_dir.chomp(GIT_DIR)
end

#tree(treeish = nil, paths = []) ⇒ Object

The Tree object for the given treeish reference

+treeish+ is the reference (default 'master')
+paths+ is an optional Array of directory paths to restrict the tree (default [])

Uses staging index if present and provides wrapper for nil treeish.

Examples 
 repo.tree('master', ['lib/'])

Returns Grit::Tree (baked)


347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/git-ds/repo.rb', line 347

def tree(treeish=nil, paths = [])
  begin 
    if staging? && (not treeish)
      @staging_index.sync
      super(@staging_index.current_tree.id, paths)
    else
      treeish = 'master' if not treeish
      super
    end
  rescue Grit::GitRuby::Repository::NoSuchPath
    
  end
end

#tree_contents(tree) ⇒ Object

Return a Hash of the contents of ‘tree’. The key is the filename, the value is the Grit object (a Tree or a Blob).



397
398
399
400
# File 'lib/git-ds/repo.rb', line 397

def tree_contents(tree)
  return {} if not tree
  tree.contents.inject({}) { |h,item| h[item.name] = item; h }
end

#unstageObject

Close staging index. This discards all (non-committed) changes in the staging index.



223
224
225
# File 'lib/git-ds/repo.rb', line 223

def unstage
  self.staging=(nil)
end