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
branchwithref(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
fromto this GitRepo’s path. - #commit!(message) ⇒ Object
-
#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
rootpoints to an existing git repo. -
#git_dir ⇒ Object
This repo’s
.gitdirectory, where git stores its objects and other repo data. -
#include?(ref) ⇒ Boolean
True if the commit referenced by
refis present somewhere in this repo. -
#init!(gitignore_contents = '') ⇒ Object
Initialize this repository, if it doesn’t already exist, using the supplied gitignore contents (or a blank one).
-
#initialize(path, opts = {}) ⇒ 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, &block) ⇒ Object
Run
cmdon the shell, changing to this repo’s #root. -
#repo_shell?(*cmd) ⇒ Boolean
Run
cmdon the shell usingshell?. -
#repo_shell_as_owner(*cmd, &block) ⇒ Object
Run
cmdviarepo_shell, sudoing as the owner of the repository if therun_as_owner?flag is set. -
#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
refcurrently refers to. -
#resolve_full(ref) ⇒ Object
The full SHA of the commit that
refcurrently refers to. -
#root ⇒ Object
This repo’s top-level directory.
- #run_as_owner? ⇒ Boolean
-
#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, opts = {}) ⇒ GitRepo
Returns a new instance of GitRepo.
46 47 48 49 |
# File 'lib/babushka/git_repo.rb', line 46 def initialize path, opts = {} @path = path.p @run_as_owner = !!opts[:run_as_owner] 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.
139 140 141 142 |
# File 'lib/babushka/git_repo.rb', line 139 def ahead? !remote_branch_exists? || !repo_shell("git rev-list #{current_remote_branch}..").split("\n").empty? end |
#all_branches ⇒ Object
199 200 201 202 |
# File 'lib/babushka/git_repo.rb', line 199 def all_branches names = repo_shell('git show-ref') || "" names.split("\n").collapse(%r{^\w+ refs/(?:heads|remotes)/}) - ['origin/HEAD'] end |
#applying? ⇒ Boolean
True if a patch is partway through being applied – perhaps because applying it caused conflicts that are yet to be resolved.
162 163 164 165 166 |
# File 'lib/babushka/git_repo.rb', line 162 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.
146 147 148 149 |
# File 'lib/babushka/git_repo.rb', line 146 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.
175 176 177 |
# File 'lib/babushka/git_repo.rb', line 175 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.
291 292 293 |
# File 'lib/babushka/git_repo.rb', line 291 def branch! branch, ref = 'HEAD' repo_shell_as_owner("git branch '#{branch}' '#{ref}'") end |
#branches ⇒ Object
An array of the names of all the local branches in this repo.
194 195 196 197 |
# File 'lib/babushka/git_repo.rb', line 194 def branches names = repo_shell('git show-ref --heads') || "" names.split("\n").collapse(%r{^\w+ refs/heads/}) end |
#checkout!(ref) ⇒ Object
Check out the supplied ref, detaching the HEAD if the named ref isn’t a branch.
304 305 306 |
# File 'lib/babushka/git_repo.rb', line 304 def checkout! ref repo_shell_as_owner("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.
117 118 119 120 |
# File 'lib/babushka/git_repo.rb', line 117 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.
278 279 280 281 282 283 |
# File 'lib/babushka/git_repo.rb', line 278 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 |
#commit!(message) ⇒ Object
285 286 287 |
# File 'lib/babushka/git_repo.rb', line 285 def commit! repo_shell_as_owner("git", "commit", "-m", ) 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.
207 208 209 210 211 212 213 214 215 |
# File 'lib/babushka/git_repo.rb', line 207 def current_branch # Can't use --short because many VPS gits lack it. symbolic_ref = repo_shell?("git symbolic-ref -q HEAD") if symbolic_ref symbolic_ref.sub(%r{^refs/heads/}, '') else current_full_head end end |
#current_full_head ⇒ Object
The full 40 character SHA of the current HEAD.
232 233 234 |
# File 'lib/babushka/git_repo.rb', line 232 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.
227 228 229 |
# File 'lib/babushka/git_repo.rb', line 227 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.
220 221 222 223 |
# File 'lib/babushka/git_repo.rb', line 220 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.
310 311 312 |
# File 'lib/babushka/git_repo.rb', line 310 def detach! ref = 'HEAD' repo_shell_as_owner("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.
124 125 126 |
# File 'lib/babushka/git_repo.rb', line 124 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.
70 71 72 |
# File 'lib/babushka/git_repo.rb', line 70 def exists? !root.nil? && root.exists? end |
#git_dir ⇒ Object
This repo’s .git directory, where git stores its objects and other repo data.
61 62 63 |
# File 'lib/babushka/git_repo.rb', line 61 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.
132 133 134 |
# File 'lib/babushka/git_repo.rb', line 132 def include? ref repo_shell("git rev-list -n 1 '#{ref}'") end |
#init!(gitignore_contents = '') ⇒ Object
Initialize this repository, if it doesn’t already exist, using the supplied gitignore contents (or a blank one).
266 267 268 269 270 271 272 273 274 |
# File 'lib/babushka/git_repo.rb', line 266 def init! gitignore_contents = '' if !exists? path.mkdir shell('git init', :cd => path) (path / '.gitignore').write(gitignore_contents) shell('git add .gitignore', :cd => path) shell('git commit -m "Add .gitignore."', :cd => path) end end |
#inspect ⇒ Object
320 321 322 323 324 325 326 |
# File 'lib/babushka/git_repo.rb', line 320 def inspect if !exists? "#<GitRepo:#{path} (nonexistent)>" else "#<GitRepo:#{root} : #{current_branch}@#{current_head}#{' (dirty)' if dirty?}>" end end |
#merging? ⇒ Boolean
True if a merge is in progress – perhaps because it produced conflicts that are yet to be resolved.
170 171 172 |
# File 'lib/babushka/git_repo.rb', line 170 def merging? (git_dir / 'MERGE_HEAD').exists? or rebase_merging? end |
#rebase_merging? ⇒ Boolean
179 180 181 182 183 |
# File 'lib/babushka/git_repo.rb', line 179 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.
154 155 156 157 158 |
# File 'lib/babushka/git_repo.rb', line 154 def rebasing? %w[rebase rebase-apply ../.dotest].any? {|d| (git_dir / d).exists? } or rebase_merging? or rebasing_interactively? end |
#rebasing_interactively? ⇒ Boolean
185 186 187 188 189 |
# File 'lib/babushka/git_repo.rb', line 185 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.
260 261 262 |
# File 'lib/babushka/git_repo.rb', line 260 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.
254 255 256 |
# File 'lib/babushka/git_repo.rb', line 254 def remote_for branch repo_shell?("git config branch.#{branch}.remote") || 'origin' end |
#repo_shell(*cmd, &block) ⇒ Object
79 80 81 82 83 84 85 86 |
# File 'lib/babushka/git_repo.rb', line 79 def repo_shell *cmd, &block if !exists? raise GitRepoError, "There is no repo at #{@path}." else opts = cmd. shell(*cmd.push(opts.merge(:cd => root)), &block) end end |
#repo_shell?(*cmd) ⇒ Boolean
Run cmd on the shell using shell?.
The semantics of this command are identical to those of repo_shell, except that shell? is used to invoke the command instead of shell.
(See the ShellHelpers docs for details on those two methods).
94 95 96 97 98 99 100 101 |
# File 'lib/babushka/git_repo.rb', line 94 def repo_shell? *cmd if !exists? raise GitRepoError, "There is no repo at #{@path}." else opts = cmd. shell?(*cmd.push(opts.merge(:cd => root))) end end |
#repo_shell_as_owner(*cmd, &block) ⇒ Object
Run cmd via repo_shell, sudoing as the owner of the repository if the run_as_owner? flag is set.
This command is useful for cleanly working with a root-owned repo without having to run babushka as root.
108 109 110 111 112 113 |
# File 'lib/babushka/git_repo.rb', line 108 def repo_shell_as_owner *cmd, &block opts = cmd. opts[:as] = root.owner if run_as_owner? repo_shell(*cmd.push(opts), &block) end |
#reset_hard!(ref = 'HEAD') ⇒ Object
Reset the repo to the given ref, discarding changes in the index and working copy.
316 317 318 |
# File 'lib/babushka/git_repo.rb', line 316 def reset_hard! ref = 'HEAD' repo_shell_as_owner("git reset --hard '#{ref}'") end |
#resolve(ref) ⇒ Object
The short SHA of the commit that ref currently refers to.
Note that it’s possible for git’s short SHA to change when new commits enter the repo (via commit or fetch), as git adds characters to keep short SHAs unique.
241 242 243 |
# File 'lib/babushka/git_repo.rb', line 241 def resolve ref repo_shell?("git rev-parse --short #{ref}^{commit}") end |
#resolve_full(ref) ⇒ Object
The full SHA of the commit that ref currently refers to.
246 247 248 |
# File 'lib/babushka/git_repo.rb', line 246 def resolve_full ref repo_shell?("git rev-parse #{ref}^{commit}") end |
#root ⇒ Object
This repo’s top-level directory.
56 57 58 |
# File 'lib/babushka/git_repo.rb', line 56 def root @root ||= self.class.repo_for(path) end |
#run_as_owner? ⇒ Boolean
51 52 53 |
# File 'lib/babushka/git_repo.rb', line 51 def run_as_owner? @run_as_owner 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.
298 299 300 |
# File 'lib/babushka/git_repo.rb', line 298 def track! branch repo_shell_as_owner("git checkout -t '#{branch}' -b '#{branch.sub(%r{^.*/}, '')}'") end |