class Git::Base
The main public interface for interacting with Git commands
Instead of creating a Git::Base directly, obtain a Git::Base instance by calling one of the follow {Git} class methods: {Git.open}, {Git.init}, {Git.clone}, or {Git.bare}.
@api public
Public Class Methods
(see Git.bare)
# File lib/git/base.rb, line 15 def self.bare(git_dir, options = {}) normalize_paths(options, default_repository: git_dir, bare: true) self.new(options) end
(see Git.clone)
# File lib/git/base.rb, line 21 def self.clone(repository_url, directory, options = {}) new_options = Git::Lib.new(nil, options[:log]).clone(repository_url, directory, options) normalize_paths(new_options, bare: options[:bare] || options[:mirror]) new(new_options) end
Returns (and initialize if needed) a Git::Config instance
@return [Git::Config] the current config instance.
# File lib/git/base.rb, line 35 def self.config @@config ||= Config.new end
(see Git.init)
# File lib/git/base.rb, line 40 def self.init(directory = '.', options = {}) normalize_paths(options, default_working_directory: directory, default_repository: directory, bare: options[:bare]) init_options = { :bare => options[:bare], :initial_branch => options[:initial_branch] } directory = options[:bare] ? options[:repository] : options[:working_directory] FileUtils.mkdir_p(directory) unless File.exist?(directory) # TODO: this dance seems awkward: this creates a Git::Lib so we can call # init so we can create a new Git::Base which in turn (ultimately) # creates another/different Git::Lib. # # TODO: maybe refactor so this Git::Bare.init does this: # self.new(opts).init(init_opts) and move all/some of this code into # Git::Bare#init. This way the init method can be called on any # repository you have a Git::Base instance for. This would not # change the existing interface (other than adding to it). # Git::Lib.new(options).init(init_options) self.new(options) end
Create an object that executes Git commands in the context of a working copy or a bare repository.
@param [Hash] options The options for this command (see list of valid
options below)
@option options [Pathname] :working_dir the path to the root of the working
directory. Should be `nil` if executing commands on a bare repository.
@option options [Pathname] :repository used to specify a non-standard path to
the repository directory. The default is `"#{working_dir}/.git"`.
@option options [Pathname] :index used to specify a non-standard path to an
index file. The default is `"#{working_dir}/.git/index"`
@option options [Logger] :log A logger to use for Git operations. Git
commands are logged at the `:info` level. Additional logging is done at the `:debug` level.
@return [Git::Base] an object that can execute git commands in the context
of the opened working copy or bare repository
# File lib/git/base.rb, line 111 def initialize(options = {}) if working_dir = options[:working_directory] options[:repository] ||= File.join(working_dir, '.git') options[:index] ||= File.join(options[:repository], 'index') end @logger = (options[:log] || Logger.new(nil)) @logger.info("Starting Git") @working_directory = options[:working_directory] ? Git::WorkingDirectory.new(options[:working_directory]) : nil @repository = options[:repository] ? Git::Repository.new(options[:repository]) : nil @index = options[:index] ? Git::Index.new(options[:index], false) : nil end
(see Git.open)
# File lib/git/base.rb, line 79 def self.open(working_dir, options = {}) raise ArgumentError, "'#{working_dir}' is not a directory" unless Dir.exist?(working_dir) working_dir = root_of_worktree(working_dir) unless options[:repository] normalize_paths(options, default_working_directory: working_dir) self.new(options) end
(see Git.default_branch)
# File lib/git/base.rb, line 28 def self.repository_default_branch(repository, options = {}) Git::Lib.new(nil, options[:log]).repository_default_branch(repository) end
# File lib/git/base.rb, line 66 def self.root_of_worktree(working_dir) result = working_dir status = nil git_cmd = "#{Git::Base.config.binary_path} -c core.quotePath=true -c color.ui=false rev-parse --show-toplevel 2>&1" result, status = Open3.capture2(git_cmd, chdir: File.expand_path(working_dir)) result = result.chomp raise ArgumentError, "'#{working_dir}' is not in a git working tree" unless status.success? result end
Private Class Methods
Normalize options before they are sent to ::new
Updates the options parameter by setting appropriate values for the following keys:
* options[:working_directory] * options[:repository] * options[:index]
All three values will be set to absolute paths. An exception is that :working_directory will be set to nil if bare is true.
# File lib/git/base.rb, line 760 def self.normalize_paths( options, default_working_directory: nil, default_repository: nil, bare: false ) normalize_working_directory(options, default: default_working_directory, bare: bare) normalize_repository(options, default: default_repository, bare: bare) normalize_index(options) end
Normalize options
If working with a bare repository, set to the first non-nil value out of:
1. `options[:repository]` 2. the `default` parameter 3. the current working directory
Otherwise, set to the first non-nil value of:
1. `options[:repository]` 2. `.git`
Next, if options refers to a file and not a directory, set options to the contents of that file. This is the case when working with a submodule or a secondary working tree (created with git worktree add). In these cases the repository is actually contained/nested within the parent's repository directory.
Finally, if options is a relative path, convert it to an absolute path relative to:
1. the current directory if working with a bare repository or 2. the working directory if NOT working with a bare repository
# File lib/git/base.rb, line 812 def self.normalize_repository(options, default:, bare: false) repository = if bare File.expand_path(options[:repository] || default || Dir.pwd) else File.expand_path(options[:repository] || '.git', options[:working_directory]) end if File.file?(repository) repository = File.expand_path(File.open(repository).read[8..-1].strip, options[:working_directory]) end options[:repository] = repository end
Normalize options
If working with a bare repository, set to `nil`. Otherwise, set to the first non-nil value of:
1. `options[:working_directory]`, 2. the `default` parameter, or 3. the current working directory
Finally, if options is a relative path, convert it to an absoluite path relative to the current directory.
# File lib/git/base.rb, line 779 def self.normalize_working_directory(options, default:, bare: false) working_directory = if bare nil else File.expand_path(options[:working_directory] || default || Dir.pwd) end options[:working_directory] = working_directory end
Public Instance Methods
Update the index from the current worktree to prepare the for the next commit
@example
lib.add('path/to/file') lib.add(['path/to/file1','path/to/file2']) lib.add(all: true)
@param [String, Array<String>] paths a file or files to be added to the repository (relative to the worktree root) @param [Hash] options
@option options [Boolean] :all Add, modify, and remove index entries to match the worktree @option options [Boolean] :force Allow adding otherwise ignored files
# File lib/git/base.rb, line 137 def add(paths = '.', **options) self.lib.add(paths, options) end
adds a new remote to this repository url can be a git url or a Git::Base object if it's a local reference
@git.add_remote('scotts_git', 'git://repo.or.cz/rubygit.git') @git.fetch('scotts_git') @git.merge('scotts_git/master')
Options:
:fetch => true :track => <branch_name>
# File lib/git/base.rb, line 151 def add_remote(name, url, opts = {}) url = url.repo.path if url.is_a?(Git::Base) self.lib.remote_add(name, url, opts) Git::Remote.new(self, name) end
Create a new git tag
@example
repo.add_tag('tag_name', object_reference) repo.add_tag('tag_name', object_reference, {:options => 'here'}) repo.add_tag('tag_name', {:options => 'here'})
@param [String] name The name of the tag to add @param [Hash] options Opstions to pass to `git tag`.
See [git-tag](https://git-scm.com/docs/git-tag) for more details.
@option options [boolean] :annotate Make an unsigned, annotated tag object @option options [boolean] :a An alias for the `:annotate` option @option options [boolean] :d Delete existing tag with the given names. @option options [boolean] :f Replace an existing tag with the given name (instead of failing) @option options [String] :message Use the given tag message @option options [String] :m An alias for the `:message` option @option options [boolean] :s Make a GPG-signed tag.
# File lib/git/base.rb, line 175 def add_tag(name, *options) self.lib.tag(name, *options) self.tag(name) end
# File lib/git/base.rb, line 544 def apply(file) if File.exist?(file) self.lib.apply(file) end end
# File lib/git/base.rb, line 550 def apply_mail(file) self.lib.apply_mail(file) if File.exist?(file) end
creates an archive file of the given tree-ish
# File lib/git/base.rb, line 531 def archive(treeish, file = nil, opts = {}) self.object(treeish).archive(file, opts) end
@return [Git::Branch] an object for branch_name
# File lib/git/base.rb, line 659 def branch(branch_name = self.current_branch) Git::Branch.new(self, branch_name) end
@return [Git::Branches] a collection of all the branches in the repository.
Each branch is represented as a {Git::Branch}.
# File lib/git/base.rb, line 665 def branches Git::Branches.new(self) end
# File lib/git/base.rb, line 649 def cat_file(objectish) self.lib.object_contents(objectish) end
changes current working directory for a block to the git working directory
example
@git.chdir do # write files @git.add @git.commit('message') end
# File lib/git/base.rb, line 189 def chdir # :yields: the Git::Path Dir.chdir(dir.path) do yield dir.path end end
checks out a branch as the new git working directory
# File lib/git/base.rb, line 398 def checkout(*args, **options) self.lib.checkout(*args, **options) end
checks out an old version of a file
# File lib/git/base.rb, line 403 def checkout_file(version, file) self.lib.checkout_file(version,file) end
# File lib/git/base.rb, line 588 def checkout_index(opts = {}) self.lib.checkout_index(opts) end
cleans the working directory
options:
:force :d :ff
# File lib/git/base.rb, line 344 def clean(opts = {}) self.lib.clean(opts) end
commits all pending changes in the index file to the git repository
options:
:all :allow_empty :amend :author
# File lib/git/base.rb, line 385 def commit(message, opts = {}) self.lib.commit(message, opts) end
commits all pending changes in the index file to the git repository, but automatically adds all modified files without having to explicitly calling @git.add() on them.
# File lib/git/base.rb, line 392 def commit_all(message, opts = {}) opts = {:add_all => true}.merge(opts) self.lib.commit(message, opts) end
@return [Git::Object::Commit] a commit object
# File lib/git/base.rb, line 681 def commit_tree(tree = nil, opts = {}) Git::Object::Commit.new(self, self.lib.commit_tree(tree, opts)) end
g.config('user.name', 'Scott Chacon') # sets value g.config('user.email', 'email@email.com') # sets value g.config('user.email', 'email@email.com', file: 'path/to/custom/config) # sets value in file g.config('user.name') # returns 'Scott Chacon' g.config # returns whole config hash
# File lib/git/base.rb, line 200 def config(name = nil, value = nil, options = {}) if name && value # set value lib.config_set(name, value, options) elsif name # return value lib.config_get(name) else # return hash lib.config_list end end
returns the name of the branch the working directory is currently on
# File lib/git/base.rb, line 654 def current_branch self.lib.branch_current end
deletes a tag
# File lib/git/base.rb, line 526 def delete_tag(name) self.lib.tag(name, {:d => true}) end
returns the most recent tag that is reachable from a commit
options:
:all :tags :contains :debug :exact_match :dirty :abbrev :candidates :long :always :match
# File lib/git/base.rb, line 363 def describe(committish=nil, opts={}) self.lib.describe(committish, opts) end
@return [Git::Diff] a Git::Diff object
# File lib/git/base.rb, line 686 def diff(objectish = 'HEAD', obj2 = nil) Git::Diff.new(self, objectish, obj2) end
returns a reference to the working directory
@git.dir.path @git.dir.writeable?
# File lib/git/base.rb, line 216 def dir @working_directory end
iterates over the files which are unmerged
# File lib/git/base.rb, line 447 def each_conflict(&block) # :yields: file, your_version, their_version self.lib.conflicts(&block) end
fetches changes from a remote branch - this does not modify the working directory, it just gets the changes from the remote if there are any
# File lib/git/base.rb, line 409 def fetch(remote = 'origin', opts = {}) if remote.is_a?(Hash) opts = remote remote = nil end self.lib.fetch(remote, opts) end
@return [Git::Object] a Git object
# File lib/git/base.rb, line 691 def gblob(objectish) Git::Object.new(self, objectish, 'blob') end
# File lib/git/base.rb, line 540 def gc self.lib.gc end
@return [Git::Object] a Git object
# File lib/git/base.rb, line 696 def gcommit(objectish) Git::Object.new(self, objectish, 'commit') end
Run a grep for 'string' on the HEAD of the git repository
@example Limit grep's scope by calling grep() from a specific object:
git.object("v2.3").grep('TODO')
@example Using grep results:
git.grep("TODO").each do |sha, arr| puts "in blob #{sha}:" arr.each do |line_no, match_string| puts "\t line #{line_no}: '#{match_string}'" end end
@param string [String] the string to search for @param path_limiter [String, Array] a path or array of paths to limit the search to or nil for no limit @param opts [Hash] options to pass to the underlying `git grep` command
@option opts [Boolean] :ignore_case (false) ignore case when matching @option opts [Boolean] :invert_match (false) select non-matching lines @option opts [Boolean] :extended_regexp (false) use extended regular expressions @option opts [String] :object (HEAD) the object to search from
@return [Hash<String, Array>] a hash of arrays
```Ruby { 'tree-ish1' => [[line_no1, match_string1], ...], 'tree-ish2' => [[line_no1, match_string1], ...], ... } ```
# File lib/git/base.rb, line 308 def grep(string, path_limiter = nil, opts = {}) self.object('HEAD').grep(string, path_limiter, opts) end
@return [Git::Object] a Git object
# File lib/git/base.rb, line 701 def gtree(objectish) Git::Object.new(self, objectish, 'tree') end
List the files in the worktree that are ignored by git @return [Array<String>] the list of ignored files relative to teh root of the worktree
# File lib/git/base.rb, line 315 def ignored_files self.lib.ignored_files end
returns reference to the git index file
# File lib/git/base.rb, line 221 def index @index end
returns true
if the branch exists
# File lib/git/base.rb, line 265 def is_branch?(branch) branch_names = self.branches.map {|b| b.name} branch_names.include?(branch) end
returns true
if the branch exists locally
# File lib/git/base.rb, line 253 def is_local_branch?(branch) branch_names = self.branches.local.map {|b| b.name} branch_names.include?(branch) end
returns true
if the branch exists remotely
# File lib/git/base.rb, line 259 def is_remote_branch?(branch) branch_names = self.branches.remote.map {|b| b.name} branch_names.include?(branch) end
this is a convenience method for accessing the class that wraps all the actual 'git' forked system calls. At some point I hope to replace the Git::Lib class with one that uses native methods or libgit C bindings
# File lib/git/base.rb, line 273 def lib @lib ||= Git::Lib.new(self, @logger) end
@return [Git::Log] a log with the specified number of commits
# File lib/git/base.rb, line 706 def log(count = 30) Git::Log.new(self, count) end
# File lib/git/base.rb, line 610 def ls_files(location=nil) self.lib.ls_files(location) end
# File lib/git/base.rb, line 645 def ls_tree(objectish) self.lib.ls_tree(objectish) end
merges one or more branches into the current working branch
you can specify more than one branch to merge by passing an array of branches
# File lib/git/base.rb, line 442 def merge(branch, message = 'merge', opts = {}) self.lib.merge(branch, message, opts) end
Find as good common ancestors as possible for a merge example: g.merge_base('master', 'some_branch', 'some_sha', octopus: true)
@return [Array<Git::Object::Commit>] a collection of common ancestors
# File lib/git/base.rb, line 743 def merge_base(*args) shas = self.lib.merge_base(*args) shas.map { |sha| gcommit(sha) } end
returns a Git::Object of the appropriate type you can also call @git.gtree('tree'), but that's just for readability. If you call @git.gtree('HEAD') it will still return a Git::Object::Commit object.
object calls a method that will run a rev-parse on the objectish and determine the type of the object and return an appropriate object for that type
@return [Git::Object] an instance of the appropriate type of Git::Object
# File lib/git/base.rb, line 720 def object(objectish) Git::Object.new(self, objectish) end
Pulls the given branch from the given remote into the current branch
@param remote [String] the remote repository to pull from @param branch [String] the branch to pull from @param opts [Hash] options to pass to the pull command
@option opts [Boolean] :allow_unrelated_histories (false) Merges histories of two projects that started their
lives independently
@example pulls from origin/master
@git.pull
@example pulls from upstream/master
@git.pull('upstream')
@example pulls from upstream/develop
@git.pull('upstream', 'develop')
@return [Void]
@raise [Git::FailedError] if the pull fails @raise [ArgumentError] if a branch is given without a remote
# File lib/git/base.rb, line 470 def pull(remote = nil, branch = nil, opts = {}) self.lib.pull(remote, branch, opts) end
Push changes to a remote repository
@overload push(remote = nil, branch = nil, options = {})
@param remote [String] the remote repository to push to @param branch [String] the branch to push @param options [Hash] options to pass to the push command @option opts [Boolean] :mirror (false) Push all refs under refs/heads/, refs/tags/ and refs/remotes/ @option opts [Boolean] :delete (false) Delete refs that don't exist on the remote @option opts [Boolean] :force (false) Force updates @option opts [Boolean] :tags (false) Push all refs under refs/tags/ @option opts [Array, String] :push_options (nil) Push options to transmit @return [Void] @raise [Git::FailedError] if the push fails @raise [ArgumentError] if a branch is given without a remote
# File lib/git/base.rb, line 435 def push(*args, **options) self.lib.push(*args, **options) end
# File lib/git/base.rb, line 592 def read_tree(treeish, opts = {}) self.lib.read_tree(treeish, opts) end
@return [Git::Remote] a remote of the specified name
# File lib/git/base.rb, line 725 def remote(remote_name = 'origin') Git::Remote.new(self, remote_name) end
returns an array of Git:Remote objects
# File lib/git/base.rb, line 475 def remotes self.lib.remotes.map { |r| Git::Remote.new(self, r) } end
removes a remote from this repository
@git.remove_remote('scott_git')
# File lib/git/base.rb, line 493 def remove_remote(name) self.lib.remote_remove(name) end
repacks the repository
# File lib/git/base.rb, line 536 def repack self.lib.repack end
returns reference to the git repository directory
@git.dir.path
# File lib/git/base.rb, line 227 def repo @repository end
returns the repository size in bytes
# File lib/git/base.rb, line 232 def repo_size Dir.glob(File.join(repo.path, '**', '*'), File::FNM_DOTMATCH).reject do |f| f.include?('..') end.map do |f| File.expand_path(f) end.uniq.map do |f| File.stat(f).size.to_i end.reduce(:+) end
resets the working directory to the provided commitish
# File lib/git/base.rb, line 327 def reset(commitish = nil, opts = {}) self.lib.reset(commitish, opts) end
resets the working directory to the commitish with '–hard'
# File lib/git/base.rb, line 332 def reset_hard(commitish = nil, opts = {}) opts = {:hard => true}.merge(opts) self.lib.reset(commitish, opts) end
reverts the working directory to the provided commitish. Accepts a range, such as comittish..HEAD
options:
:no_edit
# File lib/git/base.rb, line 373 def revert(commitish = nil, opts = {}) self.lib.revert(commitish, opts) end
runs git rev-parse to convert the objectish to a full sha
@example
git.revparse("HEAD^^") git.revparse('v2.4^{tree}') git.revparse('v2.4:/doc/index.html')
# File lib/git/base.rb, line 641 def revparse(objectish) self.lib.revparse(objectish) end
removes file(s) from the git repository
# File lib/git/base.rb, line 320 def rm(path = '.', opts = {}) self.lib.rm(path, opts) end
# File lib/git/base.rb, line 242 def set_index(index_file, check = true) @lib = nil @index = Git::Index.new(index_file.to_s, check) end
sets the url for a remote url can be a git url or a Git::Base object if it's a local reference
@git.set_remote_url('scotts_git', 'git://repo.or.cz/rubygit.git')
# File lib/git/base.rb, line 484 def set_remote_url(name, url) url = url.repo.path if url.is_a?(Git::Base) self.lib.remote_set_url(name, url) Git::Remote.new(self, name) end
# File lib/git/base.rb, line 247 def set_working(work_dir, check = true) @lib = nil @working_directory = Git::WorkingDirectory.new(work_dir.to_s, check) end
Shows objects
@param [String|NilClass] objectish the target object reference (nil == HEAD) @param [String|NilClass] path the path of the file to be shown @return [String] the object information
# File lib/git/base.rb, line 559 def show(objectish=nil, path=nil) self.lib.show(objectish, path) end
@return [Git::Status] a status object
# File lib/git/base.rb, line 730 def status Git::Status.new(self) end
@return [Git::Object::Tag] a tag object
# File lib/git/base.rb, line 735 def tag(tag_name) Git::Object.new(self, tag_name, 'tag', true) end
# File lib/git/base.rb, line 605 def update_ref(branch, commit) branch(branch).update_ref(commit) end
LOWER LEVEL INDEX OPERATIONS ##
# File lib/git/base.rb, line 565 def with_index(new_index) # :yields: new_index old_index = @index set_index(new_index, false) return_value = yield @index set_index(old_index) return_value end
# File lib/git/base.rb, line 573 def with_temp_index &blk # Workaround for JRUBY, since they handle the TempFile path different. # MUST be improved to be safer and OS independent. if RUBY_PLATFORM == 'java' temp_path = "/tmp/temp-index-#{(0...15).map{ ('a'..'z').to_a[rand(26)] }.join}" else tempfile = Tempfile.new('temp-index') temp_path = tempfile.path tempfile.close tempfile.unlink end with_index(temp_path, &blk) end
# File lib/git/base.rb, line 625 def with_temp_working &blk tempfile = Tempfile.new("temp-workdir") temp_dir = tempfile.path tempfile.close tempfile.unlink Dir.mkdir(temp_dir, 0700) with_working(temp_dir, &blk) end
# File lib/git/base.rb, line 614 def with_working(work_dir) # :yields: the Git::WorkingDirectory return_value = false old_working = @working_directory set_working(work_dir) Dir.chdir work_dir do return_value = yield @working_directory end set_working(old_working) return_value end
returns a Git::Worktree object for dir, commitish
# File lib/git/base.rb, line 670 def worktree(dir, commitish = nil) Git::Worktree.new(self, dir, commitish) end
returns a Git::worktrees object of all the Git::Worktrees objects for this repo
# File lib/git/base.rb, line 676 def worktrees Git::Worktrees.new(self) end
# File lib/git/base.rb, line 600 def write_and_commit_tree(opts = {}) tree = write_tree commit_tree(tree, opts) end
# File lib/git/base.rb, line 596 def write_tree self.lib.write_tree end