Class: Babushka::GitRepo
Overview
Provides some wrapper methods for interacting concisely with a git repository.
The repo is accessed by shelling out to ‘git` via the #repo_shell method, which makes sure the repo exists and that the commands are run in the right place. Hence, GitRepo doesn’t depend on your current working directory.
Most of the methods return a boolean and are used to discover things about the repository, like whether it’s #clean? or #dirty?, currently #merging? or #rebasing?, and whether it’s #ahead? or #behind? the default remote.
Some return a piece of data, like #current_head and #current_branch.
There are also some methods that make simple changes to the repository, like #checkout! and #reset_hard!.
To perform other operations on the repository like committing or rebasing, check out grit. This class is just a little ‘git` wrapper.
Instance Attribute Summary collapse
-
#path ⇒ Object
readonly
Returns the value of attribute path.
Class Method Summary collapse
-
.repo_for(path) ⇒ Object
The full path to the root of the repo #path is within, if it is within one somewhere; otherwise nil.
Instance Method Summary collapse
-
#ahead? ⇒ Boolean
True if there are any commits in the current branch’s history that aren’t also present on the corresponding remote branch, or if the remote doesn’t exist.
- #all_branches ⇒ Object
-
#applying? ⇒ Boolean
True if a patch is partway through being applied – perhaps because applying it caused conflicts that are yet to be resolved.
-
#behind? ⇒ Boolean
True if there are any commits in the current branch’s corresponding remote branch that aren’t also present locally, if the remote branch exists.
-
#bisecting? ⇒ Boolean
True if a bisect is currently in progress.
-
#branch!(branch, ref = 'HEAD') ⇒ Object
Create a new local branch called
branch
withref
(defaulting to HEAD) as its tip. -
#branches ⇒ Object
An array of the names of all the local branches in this repo.
-
#checkout!(ref) ⇒ Object
Check out the supplied ref, detaching the HEAD if the named ref isn’t a branch.
-
#clean? ⇒ Boolean
True if the repo is clean, i.e.
-
#clone!(from) ⇒ Object
Clone the remote at
from
to this GitRepo’s path. -
#current_branch ⇒ Object
The name of the branch that’s currently checked out, if any.
-
#current_full_head ⇒ Object
The full 40 character SHA of the current HEAD.
-
#current_head ⇒ Object
The short SHA of the repo’s current HEAD.
-
#current_remote_branch ⇒ Object
The namespaced name of the remote branch that the current local branch is tracking, or on origin if the branch isn’t tracking an explicit remote branch.
-
#detach!(ref = 'HEAD') ⇒ Object
Check out the supplied ref, detaching the HEAD.
-
#dirty? ⇒ Boolean
The inverse of #clean? – true if the content in the repo’s index or working copy differs from the commit HEAD refers to.
-
#exists? ⇒ Boolean
True if
root
points to an existing git repo. -
#git_dir ⇒ Object
This repo’s
.git
directory, where git stores its objects and other repo data. -
#include?(ref) ⇒ Boolean
True if the commit referenced by
ref
is present somewhere in this repo. -
#initialize(path) ⇒ GitRepo
constructor
A new instance of GitRepo.
- #inspect ⇒ Object
-
#merging? ⇒ Boolean
True if a merge is in progress – perhaps because it produced conflicts that are yet to be resolved.
- #rebase_merging? ⇒ Boolean
-
#rebasing? ⇒ Boolean
True if the repo is partway through a rebase of some kind.
- #rebasing_interactively? ⇒ Boolean
-
#remote_branch_exists? ⇒ Boolean
True if origin contains a branch of the same name as the current local branch.
-
#remote_for(branch) ⇒ Object
The remote assigned to branch in the git config, or ‘origin’ if none is set.
-
#repo_shell(cmd, opts = {}, &block) ⇒ Object
Run
cmd
on the shell, changing to this repo’s #root. - #repo_shell?(cmd, opts = {}) ⇒ Boolean
-
#reset_hard!(ref = 'HEAD') ⇒ Object
Reset the repo to the given ref, discarding changes in the index and working copy.
-
#resolve(ref) ⇒ Object
The short SHA of the commit that
ref
currently refers to. -
#root ⇒ Object
This repo’s top-level directory.
-
#track!(branch) ⇒ Object
Create a new local tracking branch for
branch
, which should be specified as remote/branch.
Methods included from ShellHelpers
cmd_dir, current_username, log_shell, login_shell, raw_shell, shell, shell!, shell?, shell_cmd, sudo, which
Methods included from LogHelpers
debug, deprecated!, log, log_block, log_error, log_ok, log_stderr, log_warn, removed!
Constructor Details
#initialize(path) ⇒ GitRepo
Returns a new instance of GitRepo.
46 47 48 |
# File 'lib/babushka/git_repo.rb', line 46 def initialize path @path = path.p end |
Instance Attribute Details
#path ⇒ Object (readonly)
Returns the value of attribute path.
32 33 34 |
# File 'lib/babushka/git_repo.rb', line 32 def path @path end |
Class Method Details
.repo_for(path) ⇒ Object
The full path to the root of the repo #path is within, if it is within one somewhere; otherwise nil.
36 37 38 39 40 41 42 43 44 |
# File 'lib/babushka/git_repo.rb', line 36 def self.repo_for path maybe = shell?("git rev-parse --git-dir", :cd => path) if path.p.dir? if maybe == '.git' path.p elsif !maybe.nil? maybe / '..' end end |
Instance Method Details
#ahead? ⇒ Boolean
True if there are any commits in the current branch’s history that aren’t also present on the corresponding remote branch, or if the remote doesn’t exist.
115 116 117 118 |
# File 'lib/babushka/git_repo.rb', line 115 def ahead? !remote_branch_exists? || !repo_shell("git rev-list #{current_remote_branch}..").split("\n").empty? end |
#all_branches ⇒ Object
175 176 177 178 179 180 181 182 |
# File 'lib/babushka/git_repo.rb', line 175 def all_branches names = repo_shell('git branch -a').split("\n").map {|l| l.sub(/^[* ]+/, '') } (names - ['(no branch)']).reject {|i| i['/origin/HEAD ->'] }.map {|i| i.sub(/^remotes\//, '') } end |
#applying? ⇒ Boolean
True if a patch is partway through being applied – perhaps because applying it caused conflicts that are yet to be resolved.
138 139 140 141 142 |
# File 'lib/babushka/git_repo.rb', line 138 def %w[rebase rebase-apply ../.dotest].any? {|d| (git_dir / d / 'applying').exists? } end |
#behind? ⇒ Boolean
True if there are any commits in the current branch’s corresponding remote branch that aren’t also present locally, if the remote branch exists.
122 123 124 125 |
# File 'lib/babushka/git_repo.rb', line 122 def behind? remote_branch_exists? && !repo_shell("git rev-list ..#{current_remote_branch}").split("\n").empty? end |
#bisecting? ⇒ Boolean
True if a bisect is currently in progress.
151 152 153 |
# File 'lib/babushka/git_repo.rb', line 151 def bisecting? (git_dir / 'BISECT_LOG').exists? end |
#branch!(branch, ref = 'HEAD') ⇒ Object
Create a new local branch called branch
with ref
(defaulting to HEAD) as its tip.
240 241 242 |
# File 'lib/babushka/git_repo.rb', line 240 def branch! branch, ref = 'HEAD' repo_shell("git branch '#{branch}' '#{ref}'") end |
#branches ⇒ Object
An array of the names of all the local branches in this repo.
170 171 172 173 |
# File 'lib/babushka/git_repo.rb', line 170 def branches names = repo_shell('git branch').split("\n").map {|l| l.sub(/^[* ]+/, '') } names - ['(no branch)'] end |
#checkout!(ref) ⇒ Object
Check out the supplied ref, detaching the HEAD if the named ref isn’t a branch.
253 254 255 |
# File 'lib/babushka/git_repo.rb', line 253 def checkout! ref repo_shell("git checkout '#{ref}'") end |
#clean? ⇒ Boolean
True if the repo is clean, i.e. when the content in its index and working copy match the commit that HEAD refers to.
93 94 95 96 |
# File 'lib/babushka/git_repo.rb', line 93 def clean? repo_shell("git status") # Sometimes git caches invalid index info; this clears it. repo_shell("git diff-index --name-status HEAD", &:stdout).blank? end |
#clone!(from) ⇒ Object
Clone the remote at from
to this GitRepo’s path. The path must be nonexistent; an error is raised if the local repo already exists.
231 232 233 234 235 236 |
# File 'lib/babushka/git_repo.rb', line 231 def clone! from raise GitRepoExists, "Can't clone #{from} to existing path #{path}." if exists? shell("git clone '#{from}' '#{path.basename}'", :cd => path.parent, :create => true) {|shell| shell.ok? || raise(GitRepoError, "Couldn't clone to #{path}: #{ shell.stderr}") } end |
#current_branch ⇒ Object
The name of the branch that’s currently checked out, if any. If there is no current branch (i.e. if the HEAD is detached), the HEAD’s SHA is returned instead.
187 188 189 |
# File 'lib/babushka/git_repo.rb', line 187 def current_branch repo_shell("cat .git/HEAD").strip.sub(/^.*refs\/heads\//, '') end |
#current_full_head ⇒ Object
The full 40 character SHA of the current HEAD.
206 207 208 |
# File 'lib/babushka/git_repo.rb', line 206 def current_full_head repo_shell("git rev-parse HEAD") end |
#current_head ⇒ Object
The short SHA of the repo’s current HEAD. This is usually 7 characters, but is longer when extra characters are required to disambiguate it.
201 202 203 |
# File 'lib/babushka/git_repo.rb', line 201 def current_head repo_shell("git rev-parse --short HEAD") end |
#current_remote_branch ⇒ Object
The namespaced name of the remote branch that the current local branch is tracking, or on origin if the branch isn’t tracking an explicit remote branch.
194 195 196 197 |
# File 'lib/babushka/git_repo.rb', line 194 def current_remote_branch branch = current_branch "#{remote_for(branch)}/#{branch}" end |
#detach!(ref = 'HEAD') ⇒ Object
Check out the supplied ref, detaching the HEAD. If the ref is a branch or tag, HEAD will reference the commit at the tip of the ref.
259 260 261 |
# File 'lib/babushka/git_repo.rb', line 259 def detach! ref = 'HEAD' repo_shell("git checkout '#{resolve(ref)}'") end |
#dirty? ⇒ Boolean
The inverse of #clean? – true if the content in the repo’s index or working copy differs from the commit HEAD refers to.
100 101 102 |
# File 'lib/babushka/git_repo.rb', line 100 def dirty? !clean? end |
#exists? ⇒ Boolean
True if root
points to an existing git repo.
The repo doesn’t always have to exist. For example, you can pass a nonexistent path when you initialize a GitRepo, and then call #clone! on it.
65 66 67 |
# File 'lib/babushka/git_repo.rb', line 65 def exists? !root.nil? && root.exists? end |
#git_dir ⇒ Object
This repo’s .git
directory, where git stores its objects and other repo data.
56 57 58 |
# File 'lib/babushka/git_repo.rb', line 56 def git_dir root / '.git' end |
#include?(ref) ⇒ Boolean
True if the commit referenced by ref
is present somewhere in this repo.
Note that the ref being present doesn’t mean that it’s a parent of HEAD
, just that it currently resolves to a commit.
108 109 110 |
# File 'lib/babushka/git_repo.rb', line 108 def include? ref repo_shell("git rev-list -n 1 '#{ref}'") end |
#inspect ⇒ Object
269 270 271 |
# File 'lib/babushka/git_repo.rb', line 269 def inspect "#<GitRepo:#{root} : #{current_branch}@#{current_head}#{' (dirty)' if dirty?}>" end |
#merging? ⇒ Boolean
True if a merge is in progress – perhaps because it produced conflicts that are yet to be resolved.
146 147 148 |
# File 'lib/babushka/git_repo.rb', line 146 def merging? (git_dir / 'MERGE_HEAD').exists? or rebase_merging? end |
#rebase_merging? ⇒ Boolean
155 156 157 158 159 |
# File 'lib/babushka/git_repo.rb', line 155 def rebase_merging? %w[rebase-merge .dotest-merge].any? {|d| (git_dir / d).exists? } end |
#rebasing? ⇒ Boolean
True if the repo is partway through a rebase of some kind. This could be because one of the commits conflicted when it was replayed, or that the rebase is interactive and is awaiting another command.
130 131 132 133 134 |
# File 'lib/babushka/git_repo.rb', line 130 def rebasing? %w[rebase rebase-apply ../.dotest].any? {|d| (git_dir / d).exists? } or rebase_merging? or rebasing_interactively? end |
#rebasing_interactively? ⇒ Boolean
161 162 163 164 165 |
# File 'lib/babushka/git_repo.rb', line 161 def rebasing_interactively? %w[rebase-merge .dotest-merge].any? {|d| (git_dir / d / 'interactive').exists? } end |
#remote_branch_exists? ⇒ Boolean
True if origin contains a branch of the same name as the current local branch.
225 226 227 |
# File 'lib/babushka/git_repo.rb', line 225 def remote_branch_exists? repo_shell?("git rev-parse refs/remotes/#{current_remote_branch}") end |
#remote_for(branch) ⇒ Object
The remote assigned to branch in the git config, or ‘origin’ if none is set. This is the remote that git pushes to and fetches from for this branch by default, and the branch that comparisons like #ahead? and #behind? are made against.
219 220 221 |
# File 'lib/babushka/git_repo.rb', line 219 def remote_for branch repo_shell?("git config branch.#{branch}.remote") || 'origin' end |
#repo_shell(cmd, opts = {}, &block) ⇒ Object
74 75 76 77 78 79 80 |
# File 'lib/babushka/git_repo.rb', line 74 def repo_shell cmd, opts = {}, &block if !exists? raise GitRepoError, "There is no repo at #{@path}." else shell cmd, opts.merge(:cd => root), &block end end |
#repo_shell?(cmd, opts = {}) ⇒ Boolean
82 83 84 85 86 87 88 |
# File 'lib/babushka/git_repo.rb', line 82 def repo_shell? cmd, opts = {} if !exists? raise GitRepoError, "There is no repo at #{@path}." else shell? cmd, opts.merge(:cd => root) end end |
#reset_hard!(ref = 'HEAD') ⇒ Object
Reset the repo to the given ref, discarding changes in the index and working copy.
265 266 267 |
# File 'lib/babushka/git_repo.rb', line 265 def reset_hard! ref = 'HEAD' repo_shell("git reset --hard '#{ref}'") end |
#resolve(ref) ⇒ Object
The short SHA of the commit that ref
currently refers to.
211 212 213 |
# File 'lib/babushka/git_repo.rb', line 211 def resolve ref repo_shell?("git rev-parse --short #{ref}") end |
#root ⇒ Object
This repo’s top-level directory.
51 52 53 |
# File 'lib/babushka/git_repo.rb', line 51 def root @root ||= self.class.repo_for(path) end |
#track!(branch) ⇒ Object
Create a new local tracking branch for branch
, which should be specified as remote/branch. For example, if “origin/next” is passed, a local ‘next’ branch will be created to track origin’s ‘next’ branch.
247 248 249 |
# File 'lib/babushka/git_repo.rb', line 247 def track! branch repo_shell("git checkout -t '#{branch}' -b '#{branch.sub(/^.*\//, '')}'") end |