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.



76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/git-ds/repo.rb', line 76

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
47
48
49
50
51
52
# File 'lib/git-ds/repo.rb', line 43

def self.create(path)
  Grit::Repo.init(path)

  # handle broken Grit init
  if not File.exist?(path + GIT_DIR)
    `git init #{path}`
  end

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



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/git-ds/repo.rb', line 57

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`.chomp
  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.



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

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

#add_filesObject




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

alias :add_files :add

#branch(tag = @current_branch) ⇒ Object

Return the Head object for the specified branch



126
127
128
# File 'lib/git-ds/repo.rb', line 126

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 “_”.



111
112
113
# File 'lib/git-ds/repo.rb', line 111

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.



135
136
137
138
139
140
141
142
143
# File 'lib/git-ds/repo.rb', line 135

def create_branch( tag=next_branch_tag(), sha=nil )
  if not sha
    sha = commits.last.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.



335
336
337
# File 'lib/git-ds/repo.rb', line 335

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.



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/git-ds/repo.rb', line 290

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.



273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/git-ds/repo.rb', line 273

def exec_in_git_dir(&block)
  curr = Dir.getwd
  result = nil
  begin
    Dir.chdir top_level
    result = yield
  rescue
    raise
  ensure
    Dir.chdir curr
  end
  result
end

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

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

Returns:

  • (Boolean)


100
101
102
# File 'lib/git-ds/repo.rb', line 100

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

#index_newObject

Return an empty git index for the repo.



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

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.



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

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(@current_branch, (path ? [path] : []))
  t ? tree_contents( t ) : {}
end

#list_blobs(path = '') ⇒ Object

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



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

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



437
438
439
# File 'lib/git-ds/repo.rb', line 437

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.



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/git-ds/repo.rb', line 172

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.



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

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.



343
344
345
346
# File 'lib/git-ds/repo.rb', line 343

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 Repo#current_branch will be used.

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



460
461
462
463
464
465
466
# File 'lib/git-ds/repo.rb', line 460

def path_to_object(path)
  treeish = (@staging_index ? staging.sha : @current_branch)
  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.



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

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.



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

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.



398
399
400
401
402
403
404
405
# File 'lib/git-ds/repo.rb', line 398

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

  (self.commits.count > 0) ? self.commits.last.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.



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/git-ds/repo.rb', line 149

def set_branch( tag, actor=nil )
  # allow creating of new branches via -b if they do not exist
  opt = (is_head? tag) ? '' : '-b'

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

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

  # Synchronize staging index (required before merge)
  unstage

  # Update current_branch info and restore staging for branch
  self.staging = @saved_stages[tag]
  self.staging.sync if staging?
  @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.



250
251
252
253
254
255
# File 'lib/git-ds/repo.rb', line 250

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.



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

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

#stagingObject Also known as: index

Return the staging index for the repo.



211
212
213
214
# File 'lib/git-ds/repo.rb', line 211

def staging
  # TODO: mutex
  @staging_index ||= StageIndex.read(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.



220
221
222
223
# File 'lib/git-ds/repo.rb', line 220

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

#staging?Boolean

Return true if a staging index is active.

Returns:

  • (Boolean)


236
237
238
# File 'lib/git-ds/repo.rb', line 236

def staging?
  @staging_index != nil
end

#tag_object(tag, sha) ⇒ Object

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



195
196
197
# File 'lib/git-ds/repo.rb', line 195

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



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

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 Repo#current_branch)
+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)


361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/git-ds/repo.rb', line 361

def tree(treeish=nil, paths = [])
  begin 
    if staging? && (not treeish)
      @staging_index.sync
      super(@staging_index.current_tree.id, paths)
    else
      treeish = @current_branch 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).



411
412
413
414
# File 'lib/git-ds/repo.rb', line 411

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.



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

def unstage
  self.staging=(nil)    # yes, the self is required. not sure why.
end